editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    scroll::scroll_amount::ScrollAmount,
    5    test::{
    6        assert_text_with_selections, build_editor,
    7        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
    8        editor_test_context::EditorTestContext,
    9        select_ranges,
   10    },
   11};
   12use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   13use futures::StreamExt;
   14use gpui::{
   15    BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
   16    WindowBounds, WindowOptions, div,
   17};
   18use indoc::indoc;
   19use language::{
   20    BracketPairConfig,
   21    Capability::ReadWrite,
   22    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   23    Override, Point,
   24    language_settings::{
   25        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   26        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   27    },
   28};
   29use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   30use lsp::CompletionParams;
   31use multi_buffer::{IndentGuide, PathKey};
   32use parking_lot::Mutex;
   33use pretty_assertions::{assert_eq, assert_ne};
   34use project::{
   35    FakeFs,
   36    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   37    project_settings::{LspSettings, ProjectSettings},
   38};
   39use serde_json::{self, json};
   40use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   41use std::{
   42    iter,
   43    sync::atomic::{self, AtomicUsize},
   44};
   45use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   46use text::ToPoint as _;
   47use unindent::Unindent;
   48use util::{
   49    assert_set_eq, path,
   50    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   51    uri,
   52};
   53use workspace::{
   54    CloseAllItems, CloseInactiveItems, NavigationEntry, ViewId,
   55    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   56};
   57
   58#[gpui::test]
   59fn test_edit_events(cx: &mut TestAppContext) {
   60    init_test(cx, |_| {});
   61
   62    let buffer = cx.new(|cx| {
   63        let mut buffer = language::Buffer::local("123456", cx);
   64        buffer.set_group_interval(Duration::from_secs(1));
   65        buffer
   66    });
   67
   68    let events = Rc::new(RefCell::new(Vec::new()));
   69    let editor1 = cx.add_window({
   70        let events = events.clone();
   71        |window, cx| {
   72            let entity = cx.entity().clone();
   73            cx.subscribe_in(
   74                &entity,
   75                window,
   76                move |_, _, event: &EditorEvent, _, _| match event {
   77                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   78                    EditorEvent::BufferEdited => {
   79                        events.borrow_mut().push(("editor1", "buffer edited"))
   80                    }
   81                    _ => {}
   82                },
   83            )
   84            .detach();
   85            Editor::for_buffer(buffer.clone(), None, window, cx)
   86        }
   87    });
   88
   89    let editor2 = cx.add_window({
   90        let events = events.clone();
   91        |window, cx| {
   92            cx.subscribe_in(
   93                &cx.entity().clone(),
   94                window,
   95                move |_, _, event: &EditorEvent, _, _| match event {
   96                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
   97                    EditorEvent::BufferEdited => {
   98                        events.borrow_mut().push(("editor2", "buffer edited"))
   99                    }
  100                    _ => {}
  101                },
  102            )
  103            .detach();
  104            Editor::for_buffer(buffer.clone(), None, window, cx)
  105        }
  106    });
  107
  108    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  109
  110    // Mutating editor 1 will emit an `Edited` event only for that editor.
  111    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  112    assert_eq!(
  113        mem::take(&mut *events.borrow_mut()),
  114        [
  115            ("editor1", "edited"),
  116            ("editor1", "buffer edited"),
  117            ("editor2", "buffer edited"),
  118        ]
  119    );
  120
  121    // Mutating editor 2 will emit an `Edited` event only for that editor.
  122    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  123    assert_eq!(
  124        mem::take(&mut *events.borrow_mut()),
  125        [
  126            ("editor2", "edited"),
  127            ("editor1", "buffer edited"),
  128            ("editor2", "buffer edited"),
  129        ]
  130    );
  131
  132    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  133    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  134    assert_eq!(
  135        mem::take(&mut *events.borrow_mut()),
  136        [
  137            ("editor1", "edited"),
  138            ("editor1", "buffer edited"),
  139            ("editor2", "buffer edited"),
  140        ]
  141    );
  142
  143    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  144    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  145    assert_eq!(
  146        mem::take(&mut *events.borrow_mut()),
  147        [
  148            ("editor1", "edited"),
  149            ("editor1", "buffer edited"),
  150            ("editor2", "buffer edited"),
  151        ]
  152    );
  153
  154    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  155    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  156    assert_eq!(
  157        mem::take(&mut *events.borrow_mut()),
  158        [
  159            ("editor2", "edited"),
  160            ("editor1", "buffer edited"),
  161            ("editor2", "buffer edited"),
  162        ]
  163    );
  164
  165    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  166    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  167    assert_eq!(
  168        mem::take(&mut *events.borrow_mut()),
  169        [
  170            ("editor2", "edited"),
  171            ("editor1", "buffer edited"),
  172            ("editor2", "buffer edited"),
  173        ]
  174    );
  175
  176    // No event is emitted when the mutation is a no-op.
  177    _ = editor2.update(cx, |editor, window, cx| {
  178        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  179
  180        editor.backspace(&Backspace, window, cx);
  181    });
  182    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  183}
  184
  185#[gpui::test]
  186fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  187    init_test(cx, |_| {});
  188
  189    let mut now = Instant::now();
  190    let group_interval = Duration::from_millis(1);
  191    let buffer = cx.new(|cx| {
  192        let mut buf = language::Buffer::local("123456", cx);
  193        buf.set_group_interval(group_interval);
  194        buf
  195    });
  196    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  197    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  198
  199    _ = editor.update(cx, |editor, window, cx| {
  200        editor.start_transaction_at(now, window, cx);
  201        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  202
  203        editor.insert("cd", window, cx);
  204        editor.end_transaction_at(now, cx);
  205        assert_eq!(editor.text(cx), "12cd56");
  206        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  207
  208        editor.start_transaction_at(now, window, cx);
  209        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  210        editor.insert("e", window, cx);
  211        editor.end_transaction_at(now, cx);
  212        assert_eq!(editor.text(cx), "12cde6");
  213        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  214
  215        now += group_interval + Duration::from_millis(1);
  216        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  217
  218        // Simulate an edit in another editor
  219        buffer.update(cx, |buffer, cx| {
  220            buffer.start_transaction_at(now, cx);
  221            buffer.edit([(0..1, "a")], None, cx);
  222            buffer.edit([(1..1, "b")], None, cx);
  223            buffer.end_transaction_at(now, cx);
  224        });
  225
  226        assert_eq!(editor.text(cx), "ab2cde6");
  227        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  228
  229        // Last transaction happened past the group interval in a different editor.
  230        // Undo it individually and don't restore selections.
  231        editor.undo(&Undo, window, cx);
  232        assert_eq!(editor.text(cx), "12cde6");
  233        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  234
  235        // First two transactions happened within the group interval in this editor.
  236        // Undo them together and restore selections.
  237        editor.undo(&Undo, window, cx);
  238        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  239        assert_eq!(editor.text(cx), "123456");
  240        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  241
  242        // Redo the first two transactions together.
  243        editor.redo(&Redo, window, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  246
  247        // Redo the last transaction on its own.
  248        editor.redo(&Redo, window, cx);
  249        assert_eq!(editor.text(cx), "ab2cde6");
  250        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  251
  252        // Test empty transactions.
  253        editor.start_transaction_at(now, window, cx);
  254        editor.end_transaction_at(now, cx);
  255        editor.undo(&Undo, window, cx);
  256        assert_eq!(editor.text(cx), "12cde6");
  257    });
  258}
  259
  260#[gpui::test]
  261fn test_ime_composition(cx: &mut TestAppContext) {
  262    init_test(cx, |_| {});
  263
  264    let buffer = cx.new(|cx| {
  265        let mut buffer = language::Buffer::local("abcde", cx);
  266        // Ensure automatic grouping doesn't occur.
  267        buffer.set_group_interval(Duration::ZERO);
  268        buffer
  269    });
  270
  271    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  272    cx.add_window(|window, cx| {
  273        let mut editor = build_editor(buffer.clone(), window, cx);
  274
  275        // Start a new IME composition.
  276        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  277        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  278        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  279        assert_eq!(editor.text(cx), "äbcde");
  280        assert_eq!(
  281            editor.marked_text_ranges(cx),
  282            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  283        );
  284
  285        // Finalize IME composition.
  286        editor.replace_text_in_range(None, "ā", window, cx);
  287        assert_eq!(editor.text(cx), "ābcde");
  288        assert_eq!(editor.marked_text_ranges(cx), None);
  289
  290        // IME composition edits are grouped and are undone/redone at once.
  291        editor.undo(&Default::default(), window, cx);
  292        assert_eq!(editor.text(cx), "abcde");
  293        assert_eq!(editor.marked_text_ranges(cx), None);
  294        editor.redo(&Default::default(), window, cx);
  295        assert_eq!(editor.text(cx), "ābcde");
  296        assert_eq!(editor.marked_text_ranges(cx), None);
  297
  298        // Start a new IME composition.
  299        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  300        assert_eq!(
  301            editor.marked_text_ranges(cx),
  302            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  303        );
  304
  305        // Undoing during an IME composition cancels it.
  306        editor.undo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "ābcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309
  310        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  311        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  312        assert_eq!(editor.text(cx), "ābcdè");
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  316        );
  317
  318        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  319        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  320        assert_eq!(editor.text(cx), "ābcdę");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with multiple cursors.
  324        editor.change_selections(None, window, cx, |s| {
  325            s.select_ranges([
  326                OffsetUtf16(1)..OffsetUtf16(1),
  327                OffsetUtf16(3)..OffsetUtf16(3),
  328                OffsetUtf16(5)..OffsetUtf16(5),
  329            ])
  330        });
  331        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  332        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  333        assert_eq!(
  334            editor.marked_text_ranges(cx),
  335            Some(vec![
  336                OffsetUtf16(0)..OffsetUtf16(3),
  337                OffsetUtf16(4)..OffsetUtf16(7),
  338                OffsetUtf16(8)..OffsetUtf16(11)
  339            ])
  340        );
  341
  342        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  343        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  344        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  345        assert_eq!(
  346            editor.marked_text_ranges(cx),
  347            Some(vec![
  348                OffsetUtf16(1)..OffsetUtf16(2),
  349                OffsetUtf16(5)..OffsetUtf16(6),
  350                OffsetUtf16(9)..OffsetUtf16(10)
  351            ])
  352        );
  353
  354        // Finalize IME composition with multiple cursors.
  355        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  356        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  357        assert_eq!(editor.marked_text_ranges(cx), None);
  358
  359        editor
  360    });
  361}
  362
  363#[gpui::test]
  364fn test_selection_with_mouse(cx: &mut TestAppContext) {
  365    init_test(cx, |_| {});
  366
  367    let editor = cx.add_window(|window, cx| {
  368        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  369        build_editor(buffer, window, cx)
  370    });
  371
  372    _ = editor.update(cx, |editor, window, cx| {
  373        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  374    });
  375    assert_eq!(
  376        editor
  377            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  378            .unwrap(),
  379        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  380    );
  381
  382    _ = editor.update(cx, |editor, window, cx| {
  383        editor.update_selection(
  384            DisplayPoint::new(DisplayRow(3), 3),
  385            0,
  386            gpui::Point::<f32>::default(),
  387            window,
  388            cx,
  389        );
  390    });
  391
  392    assert_eq!(
  393        editor
  394            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  395            .unwrap(),
  396        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  397    );
  398
  399    _ = editor.update(cx, |editor, window, cx| {
  400        editor.update_selection(
  401            DisplayPoint::new(DisplayRow(1), 1),
  402            0,
  403            gpui::Point::<f32>::default(),
  404            window,
  405            cx,
  406        );
  407    });
  408
  409    assert_eq!(
  410        editor
  411            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  412            .unwrap(),
  413        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  414    );
  415
  416    _ = editor.update(cx, |editor, window, cx| {
  417        editor.end_selection(window, cx);
  418        editor.update_selection(
  419            DisplayPoint::new(DisplayRow(3), 3),
  420            0,
  421            gpui::Point::<f32>::default(),
  422            window,
  423            cx,
  424        );
  425    });
  426
  427    assert_eq!(
  428        editor
  429            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  430            .unwrap(),
  431        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  432    );
  433
  434    _ = editor.update(cx, |editor, window, cx| {
  435        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  436        editor.update_selection(
  437            DisplayPoint::new(DisplayRow(0), 0),
  438            0,
  439            gpui::Point::<f32>::default(),
  440            window,
  441            cx,
  442        );
  443    });
  444
  445    assert_eq!(
  446        editor
  447            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  448            .unwrap(),
  449        [
  450            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  451            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  452        ]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.end_selection(window, cx);
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  464    );
  465}
  466
  467#[gpui::test]
  468fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  469    init_test(cx, |_| {});
  470
  471    let editor = cx.add_window(|window, cx| {
  472        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  473        build_editor(buffer, window, cx)
  474    });
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  478    });
  479
  480    _ = editor.update(cx, |editor, window, cx| {
  481        editor.end_selection(window, cx);
  482    });
  483
  484    _ = editor.update(cx, |editor, window, cx| {
  485        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  486    });
  487
  488    _ = editor.update(cx, |editor, window, cx| {
  489        editor.end_selection(window, cx);
  490    });
  491
  492    assert_eq!(
  493        editor
  494            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  495            .unwrap(),
  496        [
  497            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  498            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  499        ]
  500    );
  501
  502    _ = editor.update(cx, |editor, window, cx| {
  503        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  504    });
  505
  506    _ = editor.update(cx, |editor, window, cx| {
  507        editor.end_selection(window, cx);
  508    });
  509
  510    assert_eq!(
  511        editor
  512            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  513            .unwrap(),
  514        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  515    );
  516}
  517
  518#[gpui::test]
  519fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  520    init_test(cx, |_| {});
  521
  522    let editor = cx.add_window(|window, cx| {
  523        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  524        build_editor(buffer, window, cx)
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  529        assert_eq!(
  530            editor.selections.display_ranges(cx),
  531            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  532        );
  533    });
  534
  535    _ = editor.update(cx, |editor, window, cx| {
  536        editor.update_selection(
  537            DisplayPoint::new(DisplayRow(3), 3),
  538            0,
  539            gpui::Point::<f32>::default(),
  540            window,
  541            cx,
  542        );
  543        assert_eq!(
  544            editor.selections.display_ranges(cx),
  545            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  546        );
  547    });
  548
  549    _ = editor.update(cx, |editor, window, cx| {
  550        editor.cancel(&Cancel, window, cx);
  551        editor.update_selection(
  552            DisplayPoint::new(DisplayRow(1), 1),
  553            0,
  554            gpui::Point::<f32>::default(),
  555            window,
  556            cx,
  557        );
  558        assert_eq!(
  559            editor.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  561        );
  562    });
  563}
  564
  565#[gpui::test]
  566fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  567    init_test(cx, |_| {});
  568
  569    let editor = cx.add_window(|window, cx| {
  570        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  571        build_editor(buffer, window, cx)
  572    });
  573
  574    _ = editor.update(cx, |editor, window, cx| {
  575        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  576        assert_eq!(
  577            editor.selections.display_ranges(cx),
  578            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  579        );
  580
  581        editor.move_down(&Default::default(), window, cx);
  582        assert_eq!(
  583            editor.selections.display_ranges(cx),
  584            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  585        );
  586
  587        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  588        assert_eq!(
  589            editor.selections.display_ranges(cx),
  590            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  591        );
  592
  593        editor.move_up(&Default::default(), window, cx);
  594        assert_eq!(
  595            editor.selections.display_ranges(cx),
  596            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  597        );
  598    });
  599}
  600
  601#[gpui::test]
  602fn test_clone(cx: &mut TestAppContext) {
  603    init_test(cx, |_| {});
  604
  605    let (text, selection_ranges) = marked_text_ranges(
  606        indoc! {"
  607            one
  608            two
  609            threeˇ
  610            four
  611            fiveˇ
  612        "},
  613        true,
  614    );
  615
  616    let editor = cx.add_window(|window, cx| {
  617        let buffer = MultiBuffer::build_simple(&text, cx);
  618        build_editor(buffer, window, cx)
  619    });
  620
  621    _ = editor.update(cx, |editor, window, cx| {
  622        editor.change_selections(None, window, cx, |s| {
  623            s.select_ranges(selection_ranges.clone())
  624        });
  625        editor.fold_creases(
  626            vec![
  627                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  628                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  629            ],
  630            true,
  631            window,
  632            cx,
  633        );
  634    });
  635
  636    let cloned_editor = editor
  637        .update(cx, |editor, _, cx| {
  638            cx.open_window(Default::default(), |window, cx| {
  639                cx.new(|cx| editor.clone(window, cx))
  640            })
  641        })
  642        .unwrap()
  643        .unwrap();
  644
  645    let snapshot = editor
  646        .update(cx, |e, window, cx| e.snapshot(window, cx))
  647        .unwrap();
  648    let cloned_snapshot = cloned_editor
  649        .update(cx, |e, window, cx| e.snapshot(window, cx))
  650        .unwrap();
  651
  652    assert_eq!(
  653        cloned_editor
  654            .update(cx, |e, _, cx| e.display_text(cx))
  655            .unwrap(),
  656        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  657    );
  658    assert_eq!(
  659        cloned_snapshot
  660            .folds_in_range(0..text.len())
  661            .collect::<Vec<_>>(),
  662        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  663    );
  664    assert_set_eq!(
  665        cloned_editor
  666            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  667            .unwrap(),
  668        editor
  669            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  670            .unwrap()
  671    );
  672    assert_set_eq!(
  673        cloned_editor
  674            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  675            .unwrap(),
  676        editor
  677            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  678            .unwrap()
  679    );
  680}
  681
  682#[gpui::test]
  683async fn test_navigation_history(cx: &mut TestAppContext) {
  684    init_test(cx, |_| {});
  685
  686    use workspace::item::Item;
  687
  688    let fs = FakeFs::new(cx.executor());
  689    let project = Project::test(fs, [], cx).await;
  690    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  691    let pane = workspace
  692        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  693        .unwrap();
  694
  695    _ = workspace.update(cx, |_v, window, cx| {
  696        cx.new(|cx| {
  697            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  698            let mut editor = build_editor(buffer.clone(), window, cx);
  699            let handle = cx.entity();
  700            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  701
  702            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  703                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  704            }
  705
  706            // Move the cursor a small distance.
  707            // Nothing is added to the navigation history.
  708            editor.change_selections(None, window, cx, |s| {
  709                s.select_display_ranges([
  710                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  711                ])
  712            });
  713            editor.change_selections(None, window, cx, |s| {
  714                s.select_display_ranges([
  715                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  716                ])
  717            });
  718            assert!(pop_history(&mut editor, cx).is_none());
  719
  720            // Move the cursor a large distance.
  721            // The history can jump back to the previous position.
  722            editor.change_selections(None, window, cx, |s| {
  723                s.select_display_ranges([
  724                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  725                ])
  726            });
  727            let nav_entry = pop_history(&mut editor, cx).unwrap();
  728            editor.navigate(nav_entry.data.unwrap(), window, cx);
  729            assert_eq!(nav_entry.item.id(), cx.entity_id());
  730            assert_eq!(
  731                editor.selections.display_ranges(cx),
  732                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  733            );
  734            assert!(pop_history(&mut editor, cx).is_none());
  735
  736            // Move the cursor a small distance via the mouse.
  737            // Nothing is added to the navigation history.
  738            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  739            editor.end_selection(window, cx);
  740            assert_eq!(
  741                editor.selections.display_ranges(cx),
  742                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  743            );
  744            assert!(pop_history(&mut editor, cx).is_none());
  745
  746            // Move the cursor a large distance via the mouse.
  747            // The history can jump back to the previous position.
  748            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  749            editor.end_selection(window, cx);
  750            assert_eq!(
  751                editor.selections.display_ranges(cx),
  752                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  753            );
  754            let nav_entry = pop_history(&mut editor, cx).unwrap();
  755            editor.navigate(nav_entry.data.unwrap(), window, cx);
  756            assert_eq!(nav_entry.item.id(), cx.entity_id());
  757            assert_eq!(
  758                editor.selections.display_ranges(cx),
  759                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  760            );
  761            assert!(pop_history(&mut editor, cx).is_none());
  762
  763            // Set scroll position to check later
  764            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  765            let original_scroll_position = editor.scroll_manager.anchor();
  766
  767            // Jump to the end of the document and adjust scroll
  768            editor.move_to_end(&MoveToEnd, window, cx);
  769            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  770            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  771
  772            let nav_entry = pop_history(&mut editor, cx).unwrap();
  773            editor.navigate(nav_entry.data.unwrap(), window, cx);
  774            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  775
  776            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  777            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  778            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  779            let invalid_point = Point::new(9999, 0);
  780            editor.navigate(
  781                Box::new(NavigationData {
  782                    cursor_anchor: invalid_anchor,
  783                    cursor_position: invalid_point,
  784                    scroll_anchor: ScrollAnchor {
  785                        anchor: invalid_anchor,
  786                        offset: Default::default(),
  787                    },
  788                    scroll_top_row: invalid_point.row,
  789                }),
  790                window,
  791                cx,
  792            );
  793            assert_eq!(
  794                editor.selections.display_ranges(cx),
  795                &[editor.max_point(cx)..editor.max_point(cx)]
  796            );
  797            assert_eq!(
  798                editor.scroll_position(cx),
  799                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  800            );
  801
  802            editor
  803        })
  804    });
  805}
  806
  807#[gpui::test]
  808fn test_cancel(cx: &mut TestAppContext) {
  809    init_test(cx, |_| {});
  810
  811    let editor = cx.add_window(|window, cx| {
  812        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  813        build_editor(buffer, window, cx)
  814    });
  815
  816    _ = editor.update(cx, |editor, window, cx| {
  817        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  818        editor.update_selection(
  819            DisplayPoint::new(DisplayRow(1), 1),
  820            0,
  821            gpui::Point::<f32>::default(),
  822            window,
  823            cx,
  824        );
  825        editor.end_selection(window, cx);
  826
  827        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  828        editor.update_selection(
  829            DisplayPoint::new(DisplayRow(0), 3),
  830            0,
  831            gpui::Point::<f32>::default(),
  832            window,
  833            cx,
  834        );
  835        editor.end_selection(window, cx);
  836        assert_eq!(
  837            editor.selections.display_ranges(cx),
  838            [
  839                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  840                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  841            ]
  842        );
  843    });
  844
  845    _ = editor.update(cx, |editor, window, cx| {
  846        editor.cancel(&Cancel, window, cx);
  847        assert_eq!(
  848            editor.selections.display_ranges(cx),
  849            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  850        );
  851    });
  852
  853    _ = editor.update(cx, |editor, window, cx| {
  854        editor.cancel(&Cancel, window, cx);
  855        assert_eq!(
  856            editor.selections.display_ranges(cx),
  857            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  858        );
  859    });
  860}
  861
  862#[gpui::test]
  863fn test_fold_action(cx: &mut TestAppContext) {
  864    init_test(cx, |_| {});
  865
  866    let editor = cx.add_window(|window, cx| {
  867        let buffer = MultiBuffer::build_simple(
  868            &"
  869                impl Foo {
  870                    // Hello!
  871
  872                    fn a() {
  873                        1
  874                    }
  875
  876                    fn b() {
  877                        2
  878                    }
  879
  880                    fn c() {
  881                        3
  882                    }
  883                }
  884            "
  885            .unindent(),
  886            cx,
  887        );
  888        build_editor(buffer.clone(), window, cx)
  889    });
  890
  891    _ = editor.update(cx, |editor, window, cx| {
  892        editor.change_selections(None, window, cx, |s| {
  893            s.select_display_ranges([
  894                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  895            ]);
  896        });
  897        editor.fold(&Fold, window, cx);
  898        assert_eq!(
  899            editor.display_text(cx),
  900            "
  901                impl Foo {
  902                    // Hello!
  903
  904                    fn a() {
  905                        1
  906                    }
  907
  908                    fn b() {⋯
  909                    }
  910
  911                    fn c() {⋯
  912                    }
  913                }
  914            "
  915            .unindent(),
  916        );
  917
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {⋯
  923                }
  924            "
  925            .unindent(),
  926        );
  927
  928        editor.unfold_lines(&UnfoldLines, window, cx);
  929        assert_eq!(
  930            editor.display_text(cx),
  931            "
  932                impl Foo {
  933                    // Hello!
  934
  935                    fn a() {
  936                        1
  937                    }
  938
  939                    fn b() {⋯
  940                    }
  941
  942                    fn c() {⋯
  943                    }
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            editor.buffer.read(cx).read(cx).text()
  953        );
  954    });
  955}
  956
  957#[gpui::test]
  958fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  959    init_test(cx, |_| {});
  960
  961    let editor = cx.add_window(|window, cx| {
  962        let buffer = MultiBuffer::build_simple(
  963            &"
  964                class Foo:
  965                    # Hello!
  966
  967                    def a():
  968                        print(1)
  969
  970                    def b():
  971                        print(2)
  972
  973                    def c():
  974                        print(3)
  975            "
  976            .unindent(),
  977            cx,
  978        );
  979        build_editor(buffer.clone(), window, cx)
  980    });
  981
  982    _ = editor.update(cx, |editor, window, cx| {
  983        editor.change_selections(None, window, cx, |s| {
  984            s.select_display_ranges([
  985                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  986            ]);
  987        });
  988        editor.fold(&Fold, window, cx);
  989        assert_eq!(
  990            editor.display_text(cx),
  991            "
  992                class Foo:
  993                    # Hello!
  994
  995                    def a():
  996                        print(1)
  997
  998                    def b():⋯
  999
 1000                    def c():⋯
 1001            "
 1002            .unindent(),
 1003        );
 1004
 1005        editor.fold(&Fold, window, cx);
 1006        assert_eq!(
 1007            editor.display_text(cx),
 1008            "
 1009                class Foo:⋯
 1010            "
 1011            .unindent(),
 1012        );
 1013
 1014        editor.unfold_lines(&UnfoldLines, window, cx);
 1015        assert_eq!(
 1016            editor.display_text(cx),
 1017            "
 1018                class Foo:
 1019                    # Hello!
 1020
 1021                    def a():
 1022                        print(1)
 1023
 1024                    def b():⋯
 1025
 1026                    def c():⋯
 1027            "
 1028            .unindent(),
 1029        );
 1030
 1031        editor.unfold_lines(&UnfoldLines, window, cx);
 1032        assert_eq!(
 1033            editor.display_text(cx),
 1034            editor.buffer.read(cx).read(cx).text()
 1035        );
 1036    });
 1037}
 1038
 1039#[gpui::test]
 1040fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1041    init_test(cx, |_| {});
 1042
 1043    let editor = cx.add_window(|window, cx| {
 1044        let buffer = MultiBuffer::build_simple(
 1045            &"
 1046                class Foo:
 1047                    # Hello!
 1048
 1049                    def a():
 1050                        print(1)
 1051
 1052                    def b():
 1053                        print(2)
 1054
 1055
 1056                    def c():
 1057                        print(3)
 1058
 1059
 1060            "
 1061            .unindent(),
 1062            cx,
 1063        );
 1064        build_editor(buffer.clone(), window, cx)
 1065    });
 1066
 1067    _ = editor.update(cx, |editor, window, cx| {
 1068        editor.change_selections(None, window, cx, |s| {
 1069            s.select_display_ranges([
 1070                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1071            ]);
 1072        });
 1073        editor.fold(&Fold, window, cx);
 1074        assert_eq!(
 1075            editor.display_text(cx),
 1076            "
 1077                class Foo:
 1078                    # Hello!
 1079
 1080                    def a():
 1081                        print(1)
 1082
 1083                    def b():⋯
 1084
 1085
 1086                    def c():⋯
 1087
 1088
 1089            "
 1090            .unindent(),
 1091        );
 1092
 1093        editor.fold(&Fold, window, cx);
 1094        assert_eq!(
 1095            editor.display_text(cx),
 1096            "
 1097                class Foo:⋯
 1098
 1099
 1100            "
 1101            .unindent(),
 1102        );
 1103
 1104        editor.unfold_lines(&UnfoldLines, window, cx);
 1105        assert_eq!(
 1106            editor.display_text(cx),
 1107            "
 1108                class Foo:
 1109                    # Hello!
 1110
 1111                    def a():
 1112                        print(1)
 1113
 1114                    def b():⋯
 1115
 1116
 1117                    def c():⋯
 1118
 1119
 1120            "
 1121            .unindent(),
 1122        );
 1123
 1124        editor.unfold_lines(&UnfoldLines, window, cx);
 1125        assert_eq!(
 1126            editor.display_text(cx),
 1127            editor.buffer.read(cx).read(cx).text()
 1128        );
 1129    });
 1130}
 1131
 1132#[gpui::test]
 1133fn test_fold_at_level(cx: &mut TestAppContext) {
 1134    init_test(cx, |_| {});
 1135
 1136    let editor = cx.add_window(|window, cx| {
 1137        let buffer = MultiBuffer::build_simple(
 1138            &"
 1139                class Foo:
 1140                    # Hello!
 1141
 1142                    def a():
 1143                        print(1)
 1144
 1145                    def b():
 1146                        print(2)
 1147
 1148
 1149                class Bar:
 1150                    # World!
 1151
 1152                    def a():
 1153                        print(1)
 1154
 1155                    def b():
 1156                        print(2)
 1157
 1158
 1159            "
 1160            .unindent(),
 1161            cx,
 1162        );
 1163        build_editor(buffer.clone(), window, cx)
 1164    });
 1165
 1166    _ = editor.update(cx, |editor, window, cx| {
 1167        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1168        assert_eq!(
 1169            editor.display_text(cx),
 1170            "
 1171                class Foo:
 1172                    # Hello!
 1173
 1174                    def a():⋯
 1175
 1176                    def b():⋯
 1177
 1178
 1179                class Bar:
 1180                    # World!
 1181
 1182                    def a():⋯
 1183
 1184                    def b():⋯
 1185
 1186
 1187            "
 1188            .unindent(),
 1189        );
 1190
 1191        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1192        assert_eq!(
 1193            editor.display_text(cx),
 1194            "
 1195                class Foo:⋯
 1196
 1197
 1198                class Bar:⋯
 1199
 1200
 1201            "
 1202            .unindent(),
 1203        );
 1204
 1205        editor.unfold_all(&UnfoldAll, window, cx);
 1206        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():
 1217                        print(2)
 1218
 1219
 1220                class Bar:
 1221                    # World!
 1222
 1223                    def a():
 1224                        print(1)
 1225
 1226                    def b():
 1227                        print(2)
 1228
 1229
 1230            "
 1231            .unindent(),
 1232        );
 1233
 1234        assert_eq!(
 1235            editor.display_text(cx),
 1236            editor.buffer.read(cx).read(cx).text()
 1237        );
 1238    });
 1239}
 1240
 1241#[gpui::test]
 1242fn test_move_cursor(cx: &mut TestAppContext) {
 1243    init_test(cx, |_| {});
 1244
 1245    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1246    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1247
 1248    buffer.update(cx, |buffer, cx| {
 1249        buffer.edit(
 1250            vec![
 1251                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1252                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1253            ],
 1254            None,
 1255            cx,
 1256        );
 1257    });
 1258    _ = editor.update(cx, |editor, window, cx| {
 1259        assert_eq!(
 1260            editor.selections.display_ranges(cx),
 1261            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1262        );
 1263
 1264        editor.move_down(&MoveDown, window, cx);
 1265        assert_eq!(
 1266            editor.selections.display_ranges(cx),
 1267            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1268        );
 1269
 1270        editor.move_right(&MoveRight, window, cx);
 1271        assert_eq!(
 1272            editor.selections.display_ranges(cx),
 1273            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1274        );
 1275
 1276        editor.move_left(&MoveLeft, window, cx);
 1277        assert_eq!(
 1278            editor.selections.display_ranges(cx),
 1279            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1280        );
 1281
 1282        editor.move_up(&MoveUp, window, cx);
 1283        assert_eq!(
 1284            editor.selections.display_ranges(cx),
 1285            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1286        );
 1287
 1288        editor.move_to_end(&MoveToEnd, window, cx);
 1289        assert_eq!(
 1290            editor.selections.display_ranges(cx),
 1291            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1292        );
 1293
 1294        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1295        assert_eq!(
 1296            editor.selections.display_ranges(cx),
 1297            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1298        );
 1299
 1300        editor.change_selections(None, window, cx, |s| {
 1301            s.select_display_ranges([
 1302                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1303            ]);
 1304        });
 1305        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1306        assert_eq!(
 1307            editor.selections.display_ranges(cx),
 1308            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1309        );
 1310
 1311        editor.select_to_end(&SelectToEnd, window, cx);
 1312        assert_eq!(
 1313            editor.selections.display_ranges(cx),
 1314            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1315        );
 1316    });
 1317}
 1318
 1319#[gpui::test]
 1320fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1321    init_test(cx, |_| {});
 1322
 1323    let editor = cx.add_window(|window, cx| {
 1324        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1325        build_editor(buffer.clone(), window, cx)
 1326    });
 1327
 1328    assert_eq!('🟥'.len_utf8(), 4);
 1329    assert_eq!('α'.len_utf8(), 2);
 1330
 1331    _ = editor.update(cx, |editor, window, cx| {
 1332        editor.fold_creases(
 1333            vec![
 1334                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1335                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1336                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1337            ],
 1338            true,
 1339            window,
 1340            cx,
 1341        );
 1342        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1343
 1344        editor.move_right(&MoveRight, window, cx);
 1345        assert_eq!(
 1346            editor.selections.display_ranges(cx),
 1347            &[empty_range(0, "🟥".len())]
 1348        );
 1349        editor.move_right(&MoveRight, window, cx);
 1350        assert_eq!(
 1351            editor.selections.display_ranges(cx),
 1352            &[empty_range(0, "🟥🟧".len())]
 1353        );
 1354        editor.move_right(&MoveRight, window, cx);
 1355        assert_eq!(
 1356            editor.selections.display_ranges(cx),
 1357            &[empty_range(0, "🟥🟧⋯".len())]
 1358        );
 1359
 1360        editor.move_down(&MoveDown, window, cx);
 1361        assert_eq!(
 1362            editor.selections.display_ranges(cx),
 1363            &[empty_range(1, "ab⋯e".len())]
 1364        );
 1365        editor.move_left(&MoveLeft, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(1, "ab⋯".len())]
 1369        );
 1370        editor.move_left(&MoveLeft, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(1, "ab".len())]
 1374        );
 1375        editor.move_left(&MoveLeft, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "a".len())]
 1379        );
 1380
 1381        editor.move_down(&MoveDown, window, cx);
 1382        assert_eq!(
 1383            editor.selections.display_ranges(cx),
 1384            &[empty_range(2, "α".len())]
 1385        );
 1386        editor.move_right(&MoveRight, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(2, "αβ".len())]
 1390        );
 1391        editor.move_right(&MoveRight, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(2, "αβ⋯".len())]
 1395        );
 1396        editor.move_right(&MoveRight, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "αβ⋯ε".len())]
 1400        );
 1401
 1402        editor.move_up(&MoveUp, window, cx);
 1403        assert_eq!(
 1404            editor.selections.display_ranges(cx),
 1405            &[empty_range(1, "ab⋯e".len())]
 1406        );
 1407        editor.move_down(&MoveDown, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(2, "αβ⋯ε".len())]
 1411        );
 1412        editor.move_up(&MoveUp, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(1, "ab⋯e".len())]
 1416        );
 1417
 1418        editor.move_up(&MoveUp, window, cx);
 1419        assert_eq!(
 1420            editor.selections.display_ranges(cx),
 1421            &[empty_range(0, "🟥🟧".len())]
 1422        );
 1423        editor.move_left(&MoveLeft, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(0, "🟥".len())]
 1427        );
 1428        editor.move_left(&MoveLeft, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(0, "".len())]
 1432        );
 1433    });
 1434}
 1435
 1436#[gpui::test]
 1437fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1438    init_test(cx, |_| {});
 1439
 1440    let editor = cx.add_window(|window, cx| {
 1441        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1442        build_editor(buffer.clone(), window, cx)
 1443    });
 1444    _ = editor.update(cx, |editor, window, cx| {
 1445        editor.change_selections(None, window, cx, |s| {
 1446            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1447        });
 1448
 1449        // moving above start of document should move selection to start of document,
 1450        // but the next move down should still be at the original goal_x
 1451        editor.move_up(&MoveUp, window, cx);
 1452        assert_eq!(
 1453            editor.selections.display_ranges(cx),
 1454            &[empty_range(0, "".len())]
 1455        );
 1456
 1457        editor.move_down(&MoveDown, window, cx);
 1458        assert_eq!(
 1459            editor.selections.display_ranges(cx),
 1460            &[empty_range(1, "abcd".len())]
 1461        );
 1462
 1463        editor.move_down(&MoveDown, window, cx);
 1464        assert_eq!(
 1465            editor.selections.display_ranges(cx),
 1466            &[empty_range(2, "αβγ".len())]
 1467        );
 1468
 1469        editor.move_down(&MoveDown, window, cx);
 1470        assert_eq!(
 1471            editor.selections.display_ranges(cx),
 1472            &[empty_range(3, "abcd".len())]
 1473        );
 1474
 1475        editor.move_down(&MoveDown, window, cx);
 1476        assert_eq!(
 1477            editor.selections.display_ranges(cx),
 1478            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1479        );
 1480
 1481        // moving past end of document should not change goal_x
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(5, "".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(5, "".len())]
 1492        );
 1493
 1494        editor.move_up(&MoveUp, window, cx);
 1495        assert_eq!(
 1496            editor.selections.display_ranges(cx),
 1497            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1498        );
 1499
 1500        editor.move_up(&MoveUp, window, cx);
 1501        assert_eq!(
 1502            editor.selections.display_ranges(cx),
 1503            &[empty_range(3, "abcd".len())]
 1504        );
 1505
 1506        editor.move_up(&MoveUp, window, cx);
 1507        assert_eq!(
 1508            editor.selections.display_ranges(cx),
 1509            &[empty_range(2, "αβγ".len())]
 1510        );
 1511    });
 1512}
 1513
 1514#[gpui::test]
 1515fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1516    init_test(cx, |_| {});
 1517    let move_to_beg = MoveToBeginningOfLine {
 1518        stop_at_soft_wraps: true,
 1519        stop_at_indent: true,
 1520    };
 1521
 1522    let delete_to_beg = DeleteToBeginningOfLine {
 1523        stop_at_indent: false,
 1524    };
 1525
 1526    let move_to_end = MoveToEndOfLine {
 1527        stop_at_soft_wraps: true,
 1528    };
 1529
 1530    let editor = cx.add_window(|window, cx| {
 1531        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1532        build_editor(buffer, window, cx)
 1533    });
 1534    _ = editor.update(cx, |editor, window, cx| {
 1535        editor.change_selections(None, window, cx, |s| {
 1536            s.select_display_ranges([
 1537                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1538                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1539            ]);
 1540        });
 1541    });
 1542
 1543    _ = editor.update(cx, |editor, window, cx| {
 1544        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[
 1548                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1549                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1550            ]
 1551        );
 1552    });
 1553
 1554    _ = editor.update(cx, |editor, window, cx| {
 1555        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[
 1559                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1560                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1561            ]
 1562        );
 1563    });
 1564
 1565    _ = editor.update(cx, |editor, window, cx| {
 1566        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1567        assert_eq!(
 1568            editor.selections.display_ranges(cx),
 1569            &[
 1570                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1571                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1572            ]
 1573        );
 1574    });
 1575
 1576    _ = editor.update(cx, |editor, window, cx| {
 1577        editor.move_to_end_of_line(&move_to_end, window, cx);
 1578        assert_eq!(
 1579            editor.selections.display_ranges(cx),
 1580            &[
 1581                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1582                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1583            ]
 1584        );
 1585    });
 1586
 1587    // Moving to the end of line again is a no-op.
 1588    _ = editor.update(cx, |editor, window, cx| {
 1589        editor.move_to_end_of_line(&move_to_end, window, cx);
 1590        assert_eq!(
 1591            editor.selections.display_ranges(cx),
 1592            &[
 1593                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1594                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1595            ]
 1596        );
 1597    });
 1598
 1599    _ = editor.update(cx, |editor, window, cx| {
 1600        editor.move_left(&MoveLeft, window, cx);
 1601        editor.select_to_beginning_of_line(
 1602            &SelectToBeginningOfLine {
 1603                stop_at_soft_wraps: true,
 1604                stop_at_indent: true,
 1605            },
 1606            window,
 1607            cx,
 1608        );
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[
 1612                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1613                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1614            ]
 1615        );
 1616    });
 1617
 1618    _ = editor.update(cx, |editor, window, cx| {
 1619        editor.select_to_beginning_of_line(
 1620            &SelectToBeginningOfLine {
 1621                stop_at_soft_wraps: true,
 1622                stop_at_indent: true,
 1623            },
 1624            window,
 1625            cx,
 1626        );
 1627        assert_eq!(
 1628            editor.selections.display_ranges(cx),
 1629            &[
 1630                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1631                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1632            ]
 1633        );
 1634    });
 1635
 1636    _ = editor.update(cx, |editor, window, cx| {
 1637        editor.select_to_beginning_of_line(
 1638            &SelectToBeginningOfLine {
 1639                stop_at_soft_wraps: true,
 1640                stop_at_indent: true,
 1641            },
 1642            window,
 1643            cx,
 1644        );
 1645        assert_eq!(
 1646            editor.selections.display_ranges(cx),
 1647            &[
 1648                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1649                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1650            ]
 1651        );
 1652    });
 1653
 1654    _ = editor.update(cx, |editor, window, cx| {
 1655        editor.select_to_end_of_line(
 1656            &SelectToEndOfLine {
 1657                stop_at_soft_wraps: true,
 1658            },
 1659            window,
 1660            cx,
 1661        );
 1662        assert_eq!(
 1663            editor.selections.display_ranges(cx),
 1664            &[
 1665                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1666                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1667            ]
 1668        );
 1669    });
 1670
 1671    _ = editor.update(cx, |editor, window, cx| {
 1672        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1673        assert_eq!(editor.display_text(cx), "ab\n  de");
 1674        assert_eq!(
 1675            editor.selections.display_ranges(cx),
 1676            &[
 1677                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1678                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1679            ]
 1680        );
 1681    });
 1682
 1683    _ = editor.update(cx, |editor, window, cx| {
 1684        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1685        assert_eq!(editor.display_text(cx), "\n");
 1686        assert_eq!(
 1687            editor.selections.display_ranges(cx),
 1688            &[
 1689                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1691            ]
 1692        );
 1693    });
 1694}
 1695
 1696#[gpui::test]
 1697fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1698    init_test(cx, |_| {});
 1699    let move_to_beg = MoveToBeginningOfLine {
 1700        stop_at_soft_wraps: false,
 1701        stop_at_indent: false,
 1702    };
 1703
 1704    let move_to_end = MoveToEndOfLine {
 1705        stop_at_soft_wraps: false,
 1706    };
 1707
 1708    let editor = cx.add_window(|window, cx| {
 1709        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1710        build_editor(buffer, window, cx)
 1711    });
 1712
 1713    _ = editor.update(cx, |editor, window, cx| {
 1714        editor.set_wrap_width(Some(140.0.into()), cx);
 1715
 1716        // We expect the following lines after wrapping
 1717        // ```
 1718        // thequickbrownfox
 1719        // jumpedoverthelazydo
 1720        // gs
 1721        // ```
 1722        // The final `gs` was soft-wrapped onto a new line.
 1723        assert_eq!(
 1724            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1725            editor.display_text(cx),
 1726        );
 1727
 1728        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1729        // Start the cursor at the `k` on the first line
 1730        editor.change_selections(None, window, cx, |s| {
 1731            s.select_display_ranges([
 1732                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1733            ]);
 1734        });
 1735
 1736        // Moving to the beginning of the line should put us at the beginning of the line.
 1737        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1738        assert_eq!(
 1739            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1740            editor.selections.display_ranges(cx)
 1741        );
 1742
 1743        // Moving to the end of the line should put us at the end of the line.
 1744        editor.move_to_end_of_line(&move_to_end, window, cx);
 1745        assert_eq!(
 1746            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1747            editor.selections.display_ranges(cx)
 1748        );
 1749
 1750        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1751        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1752        editor.change_selections(None, window, cx, |s| {
 1753            s.select_display_ranges([
 1754                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1755            ]);
 1756        });
 1757
 1758        // Moving to the beginning of the line should put us at the start of the second line of
 1759        // display text, i.e., the `j`.
 1760        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1761        assert_eq!(
 1762            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1763            editor.selections.display_ranges(cx)
 1764        );
 1765
 1766        // Moving to the beginning of the line again should be a no-op.
 1767        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1768        assert_eq!(
 1769            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1770            editor.selections.display_ranges(cx)
 1771        );
 1772
 1773        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1774        // next display line.
 1775        editor.move_to_end_of_line(&move_to_end, window, cx);
 1776        assert_eq!(
 1777            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1778            editor.selections.display_ranges(cx)
 1779        );
 1780
 1781        // Moving to the end of the line again should be a no-op.
 1782        editor.move_to_end_of_line(&move_to_end, window, cx);
 1783        assert_eq!(
 1784            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1785            editor.selections.display_ranges(cx)
 1786        );
 1787    });
 1788}
 1789
 1790#[gpui::test]
 1791fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1792    init_test(cx, |_| {});
 1793
 1794    let move_to_beg = MoveToBeginningOfLine {
 1795        stop_at_soft_wraps: true,
 1796        stop_at_indent: true,
 1797    };
 1798
 1799    let select_to_beg = SelectToBeginningOfLine {
 1800        stop_at_soft_wraps: true,
 1801        stop_at_indent: true,
 1802    };
 1803
 1804    let delete_to_beg = DeleteToBeginningOfLine {
 1805        stop_at_indent: true,
 1806    };
 1807
 1808    let move_to_end = MoveToEndOfLine {
 1809        stop_at_soft_wraps: false,
 1810    };
 1811
 1812    let editor = cx.add_window(|window, cx| {
 1813        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1814        build_editor(buffer, window, cx)
 1815    });
 1816
 1817    _ = editor.update(cx, |editor, window, cx| {
 1818        editor.change_selections(None, window, cx, |s| {
 1819            s.select_display_ranges([
 1820                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1822            ]);
 1823        });
 1824
 1825        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1826        // and the second cursor at the first non-whitespace character in the line.
 1827        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1828        assert_eq!(
 1829            editor.selections.display_ranges(cx),
 1830            &[
 1831                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1832                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1833            ]
 1834        );
 1835
 1836        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1837        // and should move the second cursor to the beginning of the line.
 1838        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1839        assert_eq!(
 1840            editor.selections.display_ranges(cx),
 1841            &[
 1842                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1843                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1844            ]
 1845        );
 1846
 1847        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1848        // and should move the second cursor back to the first non-whitespace character in the line.
 1849        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1850        assert_eq!(
 1851            editor.selections.display_ranges(cx),
 1852            &[
 1853                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1854                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1855            ]
 1856        );
 1857
 1858        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1859        // and to the first non-whitespace character in the line for the second cursor.
 1860        editor.move_to_end_of_line(&move_to_end, window, cx);
 1861        editor.move_left(&MoveLeft, window, cx);
 1862        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1872        // and should select to the beginning of the line for the second cursor.
 1873        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1874        assert_eq!(
 1875            editor.selections.display_ranges(cx),
 1876            &[
 1877                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1878                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1879            ]
 1880        );
 1881
 1882        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1883        // and should delete to the first non-whitespace character in the line for the second cursor.
 1884        editor.move_to_end_of_line(&move_to_end, window, cx);
 1885        editor.move_left(&MoveLeft, window, cx);
 1886        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1887        assert_eq!(editor.text(cx), "c\n  f");
 1888    });
 1889}
 1890
 1891#[gpui::test]
 1892fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1893    init_test(cx, |_| {});
 1894
 1895    let editor = cx.add_window(|window, cx| {
 1896        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1897        build_editor(buffer, window, cx)
 1898    });
 1899    _ = editor.update(cx, |editor, window, cx| {
 1900        editor.change_selections(None, window, cx, |s| {
 1901            s.select_display_ranges([
 1902                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1903                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1904            ])
 1905        });
 1906
 1907        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1908        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1909
 1910        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1911        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1912
 1913        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1914        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1915
 1916        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1917        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1918
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1921
 1922        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1923        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1926        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1929        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1930
 1931        editor.move_right(&MoveRight, window, cx);
 1932        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1933        assert_selection_ranges(
 1934            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1935            editor,
 1936            cx,
 1937        );
 1938
 1939        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1940        assert_selection_ranges(
 1941            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1942            editor,
 1943            cx,
 1944        );
 1945
 1946        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1947        assert_selection_ranges(
 1948            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1949            editor,
 1950            cx,
 1951        );
 1952    });
 1953}
 1954
 1955#[gpui::test]
 1956fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1957    init_test(cx, |_| {});
 1958
 1959    let editor = cx.add_window(|window, cx| {
 1960        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1961        build_editor(buffer, window, cx)
 1962    });
 1963
 1964    _ = editor.update(cx, |editor, window, cx| {
 1965        editor.set_wrap_width(Some(140.0.into()), cx);
 1966        assert_eq!(
 1967            editor.display_text(cx),
 1968            "use one::{\n    two::three::\n    four::five\n};"
 1969        );
 1970
 1971        editor.change_selections(None, window, cx, |s| {
 1972            s.select_display_ranges([
 1973                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1974            ]);
 1975        });
 1976
 1977        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1978        assert_eq!(
 1979            editor.selections.display_ranges(cx),
 1980            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1981        );
 1982
 1983        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1984        assert_eq!(
 1985            editor.selections.display_ranges(cx),
 1986            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1987        );
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 1999        );
 2000
 2001        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2011        );
 2012    });
 2013}
 2014
 2015#[gpui::test]
 2016async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2017    init_test(cx, |_| {});
 2018    let mut cx = EditorTestContext::new(cx).await;
 2019
 2020    let line_height = cx.editor(|editor, window, _| {
 2021        editor
 2022            .style()
 2023            .unwrap()
 2024            .text
 2025            .line_height_in_pixels(window.rem_size())
 2026    });
 2027    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2028
 2029    cx.set_state(
 2030        &r#"ˇone
 2031        two
 2032
 2033        three
 2034        fourˇ
 2035        five
 2036
 2037        six"#
 2038            .unindent(),
 2039    );
 2040
 2041    cx.update_editor(|editor, window, cx| {
 2042        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2043    });
 2044    cx.assert_editor_state(
 2045        &r#"one
 2046        two
 2047        ˇ
 2048        three
 2049        four
 2050        five
 2051        ˇ
 2052        six"#
 2053            .unindent(),
 2054    );
 2055
 2056    cx.update_editor(|editor, window, cx| {
 2057        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2058    });
 2059    cx.assert_editor_state(
 2060        &r#"one
 2061        two
 2062
 2063        three
 2064        four
 2065        five
 2066        ˇ
 2067        sixˇ"#
 2068            .unindent(),
 2069    );
 2070
 2071    cx.update_editor(|editor, window, cx| {
 2072        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2073    });
 2074    cx.assert_editor_state(
 2075        &r#"one
 2076        two
 2077
 2078        three
 2079        four
 2080        five
 2081
 2082        sixˇ"#
 2083            .unindent(),
 2084    );
 2085
 2086    cx.update_editor(|editor, window, cx| {
 2087        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2088    });
 2089    cx.assert_editor_state(
 2090        &r#"one
 2091        two
 2092
 2093        three
 2094        four
 2095        five
 2096        ˇ
 2097        six"#
 2098            .unindent(),
 2099    );
 2100
 2101    cx.update_editor(|editor, window, cx| {
 2102        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2103    });
 2104    cx.assert_editor_state(
 2105        &r#"one
 2106        two
 2107        ˇ
 2108        three
 2109        four
 2110        five
 2111
 2112        six"#
 2113            .unindent(),
 2114    );
 2115
 2116    cx.update_editor(|editor, window, cx| {
 2117        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2118    });
 2119    cx.assert_editor_state(
 2120        &r#"ˇone
 2121        two
 2122
 2123        three
 2124        four
 2125        five
 2126
 2127        six"#
 2128            .unindent(),
 2129    );
 2130}
 2131
 2132#[gpui::test]
 2133async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2134    init_test(cx, |_| {});
 2135    let mut cx = EditorTestContext::new(cx).await;
 2136    let line_height = cx.editor(|editor, window, _| {
 2137        editor
 2138            .style()
 2139            .unwrap()
 2140            .text
 2141            .line_height_in_pixels(window.rem_size())
 2142    });
 2143    let window = cx.window;
 2144    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2145
 2146    cx.set_state(
 2147        r#"ˇone
 2148        two
 2149        three
 2150        four
 2151        five
 2152        six
 2153        seven
 2154        eight
 2155        nine
 2156        ten
 2157        "#,
 2158    );
 2159
 2160    cx.update_editor(|editor, window, cx| {
 2161        assert_eq!(
 2162            editor.snapshot(window, cx).scroll_position(),
 2163            gpui::Point::new(0., 0.)
 2164        );
 2165        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2166        assert_eq!(
 2167            editor.snapshot(window, cx).scroll_position(),
 2168            gpui::Point::new(0., 3.)
 2169        );
 2170        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2171        assert_eq!(
 2172            editor.snapshot(window, cx).scroll_position(),
 2173            gpui::Point::new(0., 6.)
 2174        );
 2175        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2176        assert_eq!(
 2177            editor.snapshot(window, cx).scroll_position(),
 2178            gpui::Point::new(0., 3.)
 2179        );
 2180
 2181        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2182        assert_eq!(
 2183            editor.snapshot(window, cx).scroll_position(),
 2184            gpui::Point::new(0., 1.)
 2185        );
 2186        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2187        assert_eq!(
 2188            editor.snapshot(window, cx).scroll_position(),
 2189            gpui::Point::new(0., 3.)
 2190        );
 2191    });
 2192}
 2193
 2194#[gpui::test]
 2195async fn test_autoscroll(cx: &mut TestAppContext) {
 2196    init_test(cx, |_| {});
 2197    let mut cx = EditorTestContext::new(cx).await;
 2198
 2199    let line_height = cx.update_editor(|editor, window, cx| {
 2200        editor.set_vertical_scroll_margin(2, cx);
 2201        editor
 2202            .style()
 2203            .unwrap()
 2204            .text
 2205            .line_height_in_pixels(window.rem_size())
 2206    });
 2207    let window = cx.window;
 2208    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2209
 2210    cx.set_state(
 2211        r#"ˇone
 2212            two
 2213            three
 2214            four
 2215            five
 2216            six
 2217            seven
 2218            eight
 2219            nine
 2220            ten
 2221        "#,
 2222    );
 2223    cx.update_editor(|editor, window, cx| {
 2224        assert_eq!(
 2225            editor.snapshot(window, cx).scroll_position(),
 2226            gpui::Point::new(0., 0.0)
 2227        );
 2228    });
 2229
 2230    // Add a cursor below the visible area. Since both cursors cannot fit
 2231    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2232    // allows the vertical scroll margin below that cursor.
 2233    cx.update_editor(|editor, window, cx| {
 2234        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2235            selections.select_ranges([
 2236                Point::new(0, 0)..Point::new(0, 0),
 2237                Point::new(6, 0)..Point::new(6, 0),
 2238            ]);
 2239        })
 2240    });
 2241    cx.update_editor(|editor, window, cx| {
 2242        assert_eq!(
 2243            editor.snapshot(window, cx).scroll_position(),
 2244            gpui::Point::new(0., 3.0)
 2245        );
 2246    });
 2247
 2248    // Move down. The editor cursor scrolls down to track the newest cursor.
 2249    cx.update_editor(|editor, window, cx| {
 2250        editor.move_down(&Default::default(), window, cx);
 2251    });
 2252    cx.update_editor(|editor, window, cx| {
 2253        assert_eq!(
 2254            editor.snapshot(window, cx).scroll_position(),
 2255            gpui::Point::new(0., 4.0)
 2256        );
 2257    });
 2258
 2259    // Add a cursor above the visible area. Since both cursors fit on screen,
 2260    // the editor scrolls to show both.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2263            selections.select_ranges([
 2264                Point::new(1, 0)..Point::new(1, 0),
 2265                Point::new(6, 0)..Point::new(6, 0),
 2266            ]);
 2267        })
 2268    });
 2269    cx.update_editor(|editor, window, cx| {
 2270        assert_eq!(
 2271            editor.snapshot(window, cx).scroll_position(),
 2272            gpui::Point::new(0., 1.0)
 2273        );
 2274    });
 2275}
 2276
 2277#[gpui::test]
 2278async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2279    init_test(cx, |_| {});
 2280    let mut cx = EditorTestContext::new(cx).await;
 2281
 2282    let line_height = cx.editor(|editor, window, _cx| {
 2283        editor
 2284            .style()
 2285            .unwrap()
 2286            .text
 2287            .line_height_in_pixels(window.rem_size())
 2288    });
 2289    let window = cx.window;
 2290    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2291    cx.set_state(
 2292        &r#"
 2293        ˇone
 2294        two
 2295        threeˇ
 2296        four
 2297        five
 2298        six
 2299        seven
 2300        eight
 2301        nine
 2302        ten
 2303        "#
 2304        .unindent(),
 2305    );
 2306
 2307    cx.update_editor(|editor, window, cx| {
 2308        editor.move_page_down(&MovePageDown::default(), window, cx)
 2309    });
 2310    cx.assert_editor_state(
 2311        &r#"
 2312        one
 2313        two
 2314        three
 2315        ˇfour
 2316        five
 2317        sixˇ
 2318        seven
 2319        eight
 2320        nine
 2321        ten
 2322        "#
 2323        .unindent(),
 2324    );
 2325
 2326    cx.update_editor(|editor, window, cx| {
 2327        editor.move_page_down(&MovePageDown::default(), window, cx)
 2328    });
 2329    cx.assert_editor_state(
 2330        &r#"
 2331        one
 2332        two
 2333        three
 2334        four
 2335        five
 2336        six
 2337        ˇseven
 2338        eight
 2339        nineˇ
 2340        ten
 2341        "#
 2342        .unindent(),
 2343    );
 2344
 2345    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2346    cx.assert_editor_state(
 2347        &r#"
 2348        one
 2349        two
 2350        three
 2351        ˇfour
 2352        five
 2353        sixˇ
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#
 2359        .unindent(),
 2360    );
 2361
 2362    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2363    cx.assert_editor_state(
 2364        &r#"
 2365        ˇone
 2366        two
 2367        threeˇ
 2368        four
 2369        five
 2370        six
 2371        seven
 2372        eight
 2373        nine
 2374        ten
 2375        "#
 2376        .unindent(),
 2377    );
 2378
 2379    // Test select collapsing
 2380    cx.update_editor(|editor, window, cx| {
 2381        editor.move_page_down(&MovePageDown::default(), window, cx);
 2382        editor.move_page_down(&MovePageDown::default(), window, cx);
 2383        editor.move_page_down(&MovePageDown::default(), window, cx);
 2384    });
 2385    cx.assert_editor_state(
 2386        &r#"
 2387        one
 2388        two
 2389        three
 2390        four
 2391        five
 2392        six
 2393        seven
 2394        eight
 2395        nine
 2396        ˇten
 2397        ˇ"#
 2398        .unindent(),
 2399    );
 2400}
 2401
 2402#[gpui::test]
 2403async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2404    init_test(cx, |_| {});
 2405    let mut cx = EditorTestContext::new(cx).await;
 2406    cx.set_state("one «two threeˇ» four");
 2407    cx.update_editor(|editor, window, cx| {
 2408        editor.delete_to_beginning_of_line(
 2409            &DeleteToBeginningOfLine {
 2410                stop_at_indent: false,
 2411            },
 2412            window,
 2413            cx,
 2414        );
 2415        assert_eq!(editor.text(cx), " four");
 2416    });
 2417}
 2418
 2419#[gpui::test]
 2420fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2421    init_test(cx, |_| {});
 2422
 2423    let editor = cx.add_window(|window, cx| {
 2424        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2425        build_editor(buffer.clone(), window, cx)
 2426    });
 2427
 2428    _ = editor.update(cx, |editor, window, cx| {
 2429        editor.change_selections(None, window, cx, |s| {
 2430            s.select_display_ranges([
 2431                // an empty selection - the preceding word fragment is deleted
 2432                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2433                // characters selected - they are deleted
 2434                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2435            ])
 2436        });
 2437        editor.delete_to_previous_word_start(
 2438            &DeleteToPreviousWordStart {
 2439                ignore_newlines: false,
 2440            },
 2441            window,
 2442            cx,
 2443        );
 2444        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2445    });
 2446
 2447    _ = editor.update(cx, |editor, window, cx| {
 2448        editor.change_selections(None, window, cx, |s| {
 2449            s.select_display_ranges([
 2450                // an empty selection - the following word fragment is deleted
 2451                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2452                // characters selected - they are deleted
 2453                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2454            ])
 2455        });
 2456        editor.delete_to_next_word_end(
 2457            &DeleteToNextWordEnd {
 2458                ignore_newlines: false,
 2459            },
 2460            window,
 2461            cx,
 2462        );
 2463        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2464    });
 2465}
 2466
 2467#[gpui::test]
 2468fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2469    init_test(cx, |_| {});
 2470
 2471    let editor = cx.add_window(|window, cx| {
 2472        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2473        build_editor(buffer.clone(), window, cx)
 2474    });
 2475    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2476        ignore_newlines: false,
 2477    };
 2478    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2479        ignore_newlines: true,
 2480    };
 2481
 2482    _ = editor.update(cx, |editor, window, cx| {
 2483        editor.change_selections(None, window, cx, |s| {
 2484            s.select_display_ranges([
 2485                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2486            ])
 2487        });
 2488        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2489        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 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");
 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\n");
 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");
 2496        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2497        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 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(), "");
 2500    });
 2501}
 2502
 2503#[gpui::test]
 2504fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2505    init_test(cx, |_| {});
 2506
 2507    let editor = cx.add_window(|window, cx| {
 2508        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2509        build_editor(buffer.clone(), window, cx)
 2510    });
 2511    let del_to_next_word_end = DeleteToNextWordEnd {
 2512        ignore_newlines: false,
 2513    };
 2514    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2515        ignore_newlines: true,
 2516    };
 2517
 2518    _ = editor.update(cx, |editor, window, cx| {
 2519        editor.change_selections(None, window, cx, |s| {
 2520            s.select_display_ranges([
 2521                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2522            ])
 2523        });
 2524        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2525        assert_eq!(
 2526            editor.buffer.read(cx).read(cx).text(),
 2527            "one\n   two\nthree\n   four"
 2528        );
 2529        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2530        assert_eq!(
 2531            editor.buffer.read(cx).read(cx).text(),
 2532            "\n   two\nthree\n   four"
 2533        );
 2534        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2535        assert_eq!(
 2536            editor.buffer.read(cx).read(cx).text(),
 2537            "two\nthree\n   four"
 2538        );
 2539        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2540        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2541        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2542        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\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(), "");
 2545    });
 2546}
 2547
 2548#[gpui::test]
 2549fn test_newline(cx: &mut TestAppContext) {
 2550    init_test(cx, |_| {});
 2551
 2552    let editor = cx.add_window(|window, cx| {
 2553        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2554        build_editor(buffer.clone(), window, cx)
 2555    });
 2556
 2557    _ = editor.update(cx, |editor, window, cx| {
 2558        editor.change_selections(None, window, cx, |s| {
 2559            s.select_display_ranges([
 2560                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2561                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2562                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2563            ])
 2564        });
 2565
 2566        editor.newline(&Newline, window, cx);
 2567        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2568    });
 2569}
 2570
 2571#[gpui::test]
 2572fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2573    init_test(cx, |_| {});
 2574
 2575    let editor = cx.add_window(|window, cx| {
 2576        let buffer = MultiBuffer::build_simple(
 2577            "
 2578                a
 2579                b(
 2580                    X
 2581                )
 2582                c(
 2583                    X
 2584                )
 2585            "
 2586            .unindent()
 2587            .as_str(),
 2588            cx,
 2589        );
 2590        let mut editor = build_editor(buffer.clone(), window, cx);
 2591        editor.change_selections(None, window, cx, |s| {
 2592            s.select_ranges([
 2593                Point::new(2, 4)..Point::new(2, 5),
 2594                Point::new(5, 4)..Point::new(5, 5),
 2595            ])
 2596        });
 2597        editor
 2598    });
 2599
 2600    _ = editor.update(cx, |editor, window, cx| {
 2601        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2602        editor.buffer.update(cx, |buffer, cx| {
 2603            buffer.edit(
 2604                [
 2605                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2606                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2607                ],
 2608                None,
 2609                cx,
 2610            );
 2611            assert_eq!(
 2612                buffer.read(cx).text(),
 2613                "
 2614                    a
 2615                    b()
 2616                    c()
 2617                "
 2618                .unindent()
 2619            );
 2620        });
 2621        assert_eq!(
 2622            editor.selections.ranges(cx),
 2623            &[
 2624                Point::new(1, 2)..Point::new(1, 2),
 2625                Point::new(2, 2)..Point::new(2, 2),
 2626            ],
 2627        );
 2628
 2629        editor.newline(&Newline, window, cx);
 2630        assert_eq!(
 2631            editor.text(cx),
 2632            "
 2633                a
 2634                b(
 2635                )
 2636                c(
 2637                )
 2638            "
 2639            .unindent()
 2640        );
 2641
 2642        // The selections are moved after the inserted newlines
 2643        assert_eq!(
 2644            editor.selections.ranges(cx),
 2645            &[
 2646                Point::new(2, 0)..Point::new(2, 0),
 2647                Point::new(4, 0)..Point::new(4, 0),
 2648            ],
 2649        );
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_newline_above(cx: &mut TestAppContext) {
 2655    init_test(cx, |settings| {
 2656        settings.defaults.tab_size = NonZeroU32::new(4)
 2657    });
 2658
 2659    let language = Arc::new(
 2660        Language::new(
 2661            LanguageConfig::default(),
 2662            Some(tree_sitter_rust::LANGUAGE.into()),
 2663        )
 2664        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2665        .unwrap(),
 2666    );
 2667
 2668    let mut cx = EditorTestContext::new(cx).await;
 2669    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2670    cx.set_state(indoc! {"
 2671        const a: ˇA = (
 2672 2673                «const_functionˇ»(ˇ),
 2674                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2675 2676        ˇ);ˇ
 2677    "});
 2678
 2679    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2680    cx.assert_editor_state(indoc! {"
 2681        ˇ
 2682        const a: A = (
 2683            ˇ
 2684            (
 2685                ˇ
 2686                ˇ
 2687                const_function(),
 2688                ˇ
 2689                ˇ
 2690                ˇ
 2691                ˇ
 2692                something_else,
 2693                ˇ
 2694            )
 2695            ˇ
 2696            ˇ
 2697        );
 2698    "});
 2699}
 2700
 2701#[gpui::test]
 2702async fn test_newline_below(cx: &mut TestAppContext) {
 2703    init_test(cx, |settings| {
 2704        settings.defaults.tab_size = NonZeroU32::new(4)
 2705    });
 2706
 2707    let language = Arc::new(
 2708        Language::new(
 2709            LanguageConfig::default(),
 2710            Some(tree_sitter_rust::LANGUAGE.into()),
 2711        )
 2712        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2713        .unwrap(),
 2714    );
 2715
 2716    let mut cx = EditorTestContext::new(cx).await;
 2717    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2718    cx.set_state(indoc! {"
 2719        const a: ˇA = (
 2720 2721                «const_functionˇ»(ˇ),
 2722                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2723 2724        ˇ);ˇ
 2725    "});
 2726
 2727    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2728    cx.assert_editor_state(indoc! {"
 2729        const a: A = (
 2730            ˇ
 2731            (
 2732                ˇ
 2733                const_function(),
 2734                ˇ
 2735                ˇ
 2736                something_else,
 2737                ˇ
 2738                ˇ
 2739                ˇ
 2740                ˇ
 2741            )
 2742            ˇ
 2743        );
 2744        ˇ
 2745        ˇ
 2746    "});
 2747}
 2748
 2749#[gpui::test]
 2750async fn test_newline_comments(cx: &mut TestAppContext) {
 2751    init_test(cx, |settings| {
 2752        settings.defaults.tab_size = NonZeroU32::new(4)
 2753    });
 2754
 2755    let language = Arc::new(Language::new(
 2756        LanguageConfig {
 2757            line_comments: vec!["//".into()],
 2758            ..LanguageConfig::default()
 2759        },
 2760        None,
 2761    ));
 2762    {
 2763        let mut cx = EditorTestContext::new(cx).await;
 2764        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2765        cx.set_state(indoc! {"
 2766        // Fooˇ
 2767    "});
 2768
 2769        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2770        cx.assert_editor_state(indoc! {"
 2771        // Foo
 2772        //ˇ
 2773    "});
 2774        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2775        cx.set_state(indoc! {"
 2776        ˇ// Foo
 2777    "});
 2778        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2779        cx.assert_editor_state(indoc! {"
 2780
 2781        ˇ// Foo
 2782    "});
 2783    }
 2784    // Ensure that comment continuations can be disabled.
 2785    update_test_language_settings(cx, |settings| {
 2786        settings.defaults.extend_comment_on_newline = Some(false);
 2787    });
 2788    let mut cx = EditorTestContext::new(cx).await;
 2789    cx.set_state(indoc! {"
 2790        // Fooˇ
 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
 2799#[gpui::test]
 2800fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2801    init_test(cx, |_| {});
 2802
 2803    let editor = cx.add_window(|window, cx| {
 2804        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2805        let mut editor = build_editor(buffer.clone(), window, cx);
 2806        editor.change_selections(None, window, cx, |s| {
 2807            s.select_ranges([3..4, 11..12, 19..20])
 2808        });
 2809        editor
 2810    });
 2811
 2812    _ = editor.update(cx, |editor, window, cx| {
 2813        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2814        editor.buffer.update(cx, |buffer, cx| {
 2815            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2816            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2817        });
 2818        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2819
 2820        editor.insert("Z", window, cx);
 2821        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2822
 2823        // The selections are moved after the inserted characters
 2824        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2825    });
 2826}
 2827
 2828#[gpui::test]
 2829async fn test_tab(cx: &mut TestAppContext) {
 2830    init_test(cx, |settings| {
 2831        settings.defaults.tab_size = NonZeroU32::new(3)
 2832    });
 2833
 2834    let mut cx = EditorTestContext::new(cx).await;
 2835    cx.set_state(indoc! {"
 2836        ˇabˇc
 2837        ˇ🏀ˇ🏀ˇefg
 2838 2839    "});
 2840    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2841    cx.assert_editor_state(indoc! {"
 2842           ˇab ˇc
 2843           ˇ🏀  ˇ🏀  ˇefg
 2844        d  ˇ
 2845    "});
 2846
 2847    cx.set_state(indoc! {"
 2848        a
 2849        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2850    "});
 2851    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2852    cx.assert_editor_state(indoc! {"
 2853        a
 2854           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2855    "});
 2856}
 2857
 2858#[gpui::test]
 2859async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 2860    init_test(cx, |_| {});
 2861
 2862    let mut cx = EditorTestContext::new(cx).await;
 2863    let language = Arc::new(
 2864        Language::new(
 2865            LanguageConfig::default(),
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2869        .unwrap(),
 2870    );
 2871    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2872
 2873    // cursors that are already at the suggested indent level insert
 2874    // a soft tab. cursors that are to the left of the suggested indent
 2875    // auto-indent their line.
 2876    cx.set_state(indoc! {"
 2877        ˇ
 2878        const a: B = (
 2879            c(
 2880                d(
 2881        ˇ
 2882                )
 2883        ˇ
 2884        ˇ    )
 2885        );
 2886    "});
 2887    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2888    cx.assert_editor_state(indoc! {"
 2889            ˇ
 2890        const a: B = (
 2891            c(
 2892                d(
 2893                    ˇ
 2894                )
 2895                ˇ
 2896            ˇ)
 2897        );
 2898    "});
 2899
 2900    // handle auto-indent when there are multiple cursors on the same line
 2901    cx.set_state(indoc! {"
 2902        const a: B = (
 2903            c(
 2904        ˇ    ˇ
 2905        ˇ    )
 2906        );
 2907    "});
 2908    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2909    cx.assert_editor_state(indoc! {"
 2910        const a: B = (
 2911            c(
 2912                ˇ
 2913            ˇ)
 2914        );
 2915    "});
 2916}
 2917
 2918#[gpui::test]
 2919async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 2920    init_test(cx, |settings| {
 2921        settings.defaults.tab_size = NonZeroU32::new(3)
 2922    });
 2923
 2924    let mut cx = EditorTestContext::new(cx).await;
 2925    cx.set_state(indoc! {"
 2926         ˇ
 2927        \t ˇ
 2928        \t  ˇ
 2929        \t   ˇ
 2930         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 2931    "});
 2932
 2933    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2934    cx.assert_editor_state(indoc! {"
 2935           ˇ
 2936        \t   ˇ
 2937        \t   ˇ
 2938        \t      ˇ
 2939         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 2940    "});
 2941}
 2942
 2943#[gpui::test]
 2944async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 2945    init_test(cx, |settings| {
 2946        settings.defaults.tab_size = NonZeroU32::new(4)
 2947    });
 2948
 2949    let language = Arc::new(
 2950        Language::new(
 2951            LanguageConfig::default(),
 2952            Some(tree_sitter_rust::LANGUAGE.into()),
 2953        )
 2954        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 2955        .unwrap(),
 2956    );
 2957
 2958    let mut cx = EditorTestContext::new(cx).await;
 2959    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2960    cx.set_state(indoc! {"
 2961        fn a() {
 2962            if b {
 2963        \t ˇc
 2964            }
 2965        }
 2966    "});
 2967
 2968    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2969    cx.assert_editor_state(indoc! {"
 2970        fn a() {
 2971            if b {
 2972                ˇc
 2973            }
 2974        }
 2975    "});
 2976}
 2977
 2978#[gpui::test]
 2979async fn test_indent_outdent(cx: &mut TestAppContext) {
 2980    init_test(cx, |settings| {
 2981        settings.defaults.tab_size = NonZeroU32::new(4);
 2982    });
 2983
 2984    let mut cx = EditorTestContext::new(cx).await;
 2985
 2986    cx.set_state(indoc! {"
 2987          «oneˇ» «twoˇ»
 2988        three
 2989         four
 2990    "});
 2991    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2992    cx.assert_editor_state(indoc! {"
 2993            «oneˇ» «twoˇ»
 2994        three
 2995         four
 2996    "});
 2997
 2998    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 2999    cx.assert_editor_state(indoc! {"
 3000        «oneˇ» «twoˇ»
 3001        three
 3002         four
 3003    "});
 3004
 3005    // select across line ending
 3006    cx.set_state(indoc! {"
 3007        one two
 3008        t«hree
 3009        ˇ» four
 3010    "});
 3011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3012    cx.assert_editor_state(indoc! {"
 3013        one two
 3014            t«hree
 3015        ˇ» four
 3016    "});
 3017
 3018    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3019    cx.assert_editor_state(indoc! {"
 3020        one two
 3021        t«hree
 3022        ˇ» four
 3023    "});
 3024
 3025    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3026    cx.set_state(indoc! {"
 3027        one two
 3028        ˇthree
 3029            four
 3030    "});
 3031    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3032    cx.assert_editor_state(indoc! {"
 3033        one two
 3034            ˇthree
 3035            four
 3036    "});
 3037
 3038    cx.set_state(indoc! {"
 3039        one two
 3040        ˇ    three
 3041            four
 3042    "});
 3043    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3044    cx.assert_editor_state(indoc! {"
 3045        one two
 3046        ˇthree
 3047            four
 3048    "});
 3049}
 3050
 3051#[gpui::test]
 3052async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3053    init_test(cx, |settings| {
 3054        settings.defaults.hard_tabs = Some(true);
 3055    });
 3056
 3057    let mut cx = EditorTestContext::new(cx).await;
 3058
 3059    // select two ranges on one line
 3060    cx.set_state(indoc! {"
 3061        «oneˇ» «twoˇ»
 3062        three
 3063        four
 3064    "});
 3065    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3066    cx.assert_editor_state(indoc! {"
 3067        \t«oneˇ» «twoˇ»
 3068        three
 3069        four
 3070    "});
 3071    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3072    cx.assert_editor_state(indoc! {"
 3073        \t\t«oneˇ» «twoˇ»
 3074        three
 3075        four
 3076    "});
 3077    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3078    cx.assert_editor_state(indoc! {"
 3079        \t«oneˇ» «twoˇ»
 3080        three
 3081        four
 3082    "});
 3083    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3084    cx.assert_editor_state(indoc! {"
 3085        «oneˇ» «twoˇ»
 3086        three
 3087        four
 3088    "});
 3089
 3090    // select across a line ending
 3091    cx.set_state(indoc! {"
 3092        one two
 3093        t«hree
 3094        ˇ»four
 3095    "});
 3096    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3097    cx.assert_editor_state(indoc! {"
 3098        one two
 3099        \tt«hree
 3100        ˇ»four
 3101    "});
 3102    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3103    cx.assert_editor_state(indoc! {"
 3104        one two
 3105        \t\tt«hree
 3106        ˇ»four
 3107    "});
 3108    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3109    cx.assert_editor_state(indoc! {"
 3110        one two
 3111        \tt«hree
 3112        ˇ»four
 3113    "});
 3114    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3115    cx.assert_editor_state(indoc! {"
 3116        one two
 3117        t«hree
 3118        ˇ»four
 3119    "});
 3120
 3121    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3122    cx.set_state(indoc! {"
 3123        one two
 3124        ˇthree
 3125        four
 3126    "});
 3127    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3128    cx.assert_editor_state(indoc! {"
 3129        one two
 3130        ˇthree
 3131        four
 3132    "});
 3133    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3134    cx.assert_editor_state(indoc! {"
 3135        one two
 3136        \tˇthree
 3137        four
 3138    "});
 3139    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3140    cx.assert_editor_state(indoc! {"
 3141        one two
 3142        ˇthree
 3143        four
 3144    "});
 3145}
 3146
 3147#[gpui::test]
 3148fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3149    init_test(cx, |settings| {
 3150        settings.languages.extend([
 3151            (
 3152                "TOML".into(),
 3153                LanguageSettingsContent {
 3154                    tab_size: NonZeroU32::new(2),
 3155                    ..Default::default()
 3156                },
 3157            ),
 3158            (
 3159                "Rust".into(),
 3160                LanguageSettingsContent {
 3161                    tab_size: NonZeroU32::new(4),
 3162                    ..Default::default()
 3163                },
 3164            ),
 3165        ]);
 3166    });
 3167
 3168    let toml_language = Arc::new(Language::new(
 3169        LanguageConfig {
 3170            name: "TOML".into(),
 3171            ..Default::default()
 3172        },
 3173        None,
 3174    ));
 3175    let rust_language = Arc::new(Language::new(
 3176        LanguageConfig {
 3177            name: "Rust".into(),
 3178            ..Default::default()
 3179        },
 3180        None,
 3181    ));
 3182
 3183    let toml_buffer =
 3184        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3185    let rust_buffer =
 3186        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3187    let multibuffer = cx.new(|cx| {
 3188        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3189        multibuffer.push_excerpts(
 3190            toml_buffer.clone(),
 3191            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3192            cx,
 3193        );
 3194        multibuffer.push_excerpts(
 3195            rust_buffer.clone(),
 3196            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3197            cx,
 3198        );
 3199        multibuffer
 3200    });
 3201
 3202    cx.add_window(|window, cx| {
 3203        let mut editor = build_editor(multibuffer, window, cx);
 3204
 3205        assert_eq!(
 3206            editor.text(cx),
 3207            indoc! {"
 3208                a = 1
 3209                b = 2
 3210
 3211                const c: usize = 3;
 3212            "}
 3213        );
 3214
 3215        select_ranges(
 3216            &mut editor,
 3217            indoc! {"
 3218                «aˇ» = 1
 3219                b = 2
 3220
 3221                «const c:ˇ» usize = 3;
 3222            "},
 3223            window,
 3224            cx,
 3225        );
 3226
 3227        editor.tab(&Tab, window, cx);
 3228        assert_text_with_selections(
 3229            &mut editor,
 3230            indoc! {"
 3231                  «aˇ» = 1
 3232                b = 2
 3233
 3234                    «const c:ˇ» usize = 3;
 3235            "},
 3236            cx,
 3237        );
 3238        editor.backtab(&Backtab, window, cx);
 3239        assert_text_with_selections(
 3240            &mut editor,
 3241            indoc! {"
 3242                «aˇ» = 1
 3243                b = 2
 3244
 3245                «const c:ˇ» usize = 3;
 3246            "},
 3247            cx,
 3248        );
 3249
 3250        editor
 3251    });
 3252}
 3253
 3254#[gpui::test]
 3255async fn test_backspace(cx: &mut TestAppContext) {
 3256    init_test(cx, |_| {});
 3257
 3258    let mut cx = EditorTestContext::new(cx).await;
 3259
 3260    // Basic backspace
 3261    cx.set_state(indoc! {"
 3262        onˇe two three
 3263        fou«rˇ» five six
 3264        seven «ˇeight nine
 3265        »ten
 3266    "});
 3267    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3268    cx.assert_editor_state(indoc! {"
 3269        oˇe two three
 3270        fouˇ five six
 3271        seven ˇten
 3272    "});
 3273
 3274    // Test backspace inside and around indents
 3275    cx.set_state(indoc! {"
 3276        zero
 3277            ˇone
 3278                ˇtwo
 3279            ˇ ˇ ˇ  three
 3280        ˇ  ˇ  four
 3281    "});
 3282    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3283    cx.assert_editor_state(indoc! {"
 3284        zero
 3285        ˇone
 3286            ˇtwo
 3287        ˇ  threeˇ  four
 3288    "});
 3289}
 3290
 3291#[gpui::test]
 3292async fn test_delete(cx: &mut TestAppContext) {
 3293    init_test(cx, |_| {});
 3294
 3295    let mut cx = EditorTestContext::new(cx).await;
 3296    cx.set_state(indoc! {"
 3297        onˇe two three
 3298        fou«rˇ» five six
 3299        seven «ˇeight nine
 3300        »ten
 3301    "});
 3302    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3303    cx.assert_editor_state(indoc! {"
 3304        onˇ two three
 3305        fouˇ five six
 3306        seven ˇten
 3307    "});
 3308}
 3309
 3310#[gpui::test]
 3311fn test_delete_line(cx: &mut TestAppContext) {
 3312    init_test(cx, |_| {});
 3313
 3314    let editor = cx.add_window(|window, cx| {
 3315        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3316        build_editor(buffer, window, cx)
 3317    });
 3318    _ = editor.update(cx, |editor, window, cx| {
 3319        editor.change_selections(None, window, cx, |s| {
 3320            s.select_display_ranges([
 3321                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3322                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3323                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3324            ])
 3325        });
 3326        editor.delete_line(&DeleteLine, window, cx);
 3327        assert_eq!(editor.display_text(cx), "ghi");
 3328        assert_eq!(
 3329            editor.selections.display_ranges(cx),
 3330            vec![
 3331                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3332                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3333            ]
 3334        );
 3335    });
 3336
 3337    let editor = cx.add_window(|window, cx| {
 3338        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3339        build_editor(buffer, window, cx)
 3340    });
 3341    _ = editor.update(cx, |editor, window, cx| {
 3342        editor.change_selections(None, window, cx, |s| {
 3343            s.select_display_ranges([
 3344                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3345            ])
 3346        });
 3347        editor.delete_line(&DeleteLine, window, cx);
 3348        assert_eq!(editor.display_text(cx), "ghi\n");
 3349        assert_eq!(
 3350            editor.selections.display_ranges(cx),
 3351            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3352        );
 3353    });
 3354}
 3355
 3356#[gpui::test]
 3357fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3358    init_test(cx, |_| {});
 3359
 3360    cx.add_window(|window, cx| {
 3361        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3362        let mut editor = build_editor(buffer.clone(), window, cx);
 3363        let buffer = buffer.read(cx).as_singleton().unwrap();
 3364
 3365        assert_eq!(
 3366            editor.selections.ranges::<Point>(cx),
 3367            &[Point::new(0, 0)..Point::new(0, 0)]
 3368        );
 3369
 3370        // When on single line, replace newline at end by space
 3371        editor.join_lines(&JoinLines, window, cx);
 3372        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3373        assert_eq!(
 3374            editor.selections.ranges::<Point>(cx),
 3375            &[Point::new(0, 3)..Point::new(0, 3)]
 3376        );
 3377
 3378        // When multiple lines are selected, remove newlines that are spanned by the selection
 3379        editor.change_selections(None, window, cx, |s| {
 3380            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3381        });
 3382        editor.join_lines(&JoinLines, window, cx);
 3383        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3384        assert_eq!(
 3385            editor.selections.ranges::<Point>(cx),
 3386            &[Point::new(0, 11)..Point::new(0, 11)]
 3387        );
 3388
 3389        // Undo should be transactional
 3390        editor.undo(&Undo, window, cx);
 3391        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3392        assert_eq!(
 3393            editor.selections.ranges::<Point>(cx),
 3394            &[Point::new(0, 5)..Point::new(2, 2)]
 3395        );
 3396
 3397        // When joining an empty line don't insert a space
 3398        editor.change_selections(None, window, cx, |s| {
 3399            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3400        });
 3401        editor.join_lines(&JoinLines, window, cx);
 3402        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3403        assert_eq!(
 3404            editor.selections.ranges::<Point>(cx),
 3405            [Point::new(2, 3)..Point::new(2, 3)]
 3406        );
 3407
 3408        // We can remove trailing newlines
 3409        editor.join_lines(&JoinLines, window, cx);
 3410        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3411        assert_eq!(
 3412            editor.selections.ranges::<Point>(cx),
 3413            [Point::new(2, 3)..Point::new(2, 3)]
 3414        );
 3415
 3416        // We don't blow up on the last line
 3417        editor.join_lines(&JoinLines, window, cx);
 3418        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3419        assert_eq!(
 3420            editor.selections.ranges::<Point>(cx),
 3421            [Point::new(2, 3)..Point::new(2, 3)]
 3422        );
 3423
 3424        // reset to test indentation
 3425        editor.buffer.update(cx, |buffer, cx| {
 3426            buffer.edit(
 3427                [
 3428                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3429                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3430                ],
 3431                None,
 3432                cx,
 3433            )
 3434        });
 3435
 3436        // We remove any leading spaces
 3437        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3438        editor.change_selections(None, window, cx, |s| {
 3439            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3440        });
 3441        editor.join_lines(&JoinLines, window, cx);
 3442        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3443
 3444        // We don't insert a space for a line containing only spaces
 3445        editor.join_lines(&JoinLines, window, cx);
 3446        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3447
 3448        // We ignore any leading tabs
 3449        editor.join_lines(&JoinLines, window, cx);
 3450        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3451
 3452        editor
 3453    });
 3454}
 3455
 3456#[gpui::test]
 3457fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3458    init_test(cx, |_| {});
 3459
 3460    cx.add_window(|window, cx| {
 3461        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3462        let mut editor = build_editor(buffer.clone(), window, cx);
 3463        let buffer = buffer.read(cx).as_singleton().unwrap();
 3464
 3465        editor.change_selections(None, window, cx, |s| {
 3466            s.select_ranges([
 3467                Point::new(0, 2)..Point::new(1, 1),
 3468                Point::new(1, 2)..Point::new(1, 2),
 3469                Point::new(3, 1)..Point::new(3, 2),
 3470            ])
 3471        });
 3472
 3473        editor.join_lines(&JoinLines, window, cx);
 3474        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3475
 3476        assert_eq!(
 3477            editor.selections.ranges::<Point>(cx),
 3478            [
 3479                Point::new(0, 7)..Point::new(0, 7),
 3480                Point::new(1, 3)..Point::new(1, 3)
 3481            ]
 3482        );
 3483        editor
 3484    });
 3485}
 3486
 3487#[gpui::test]
 3488async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3489    init_test(cx, |_| {});
 3490
 3491    let mut cx = EditorTestContext::new(cx).await;
 3492
 3493    let diff_base = r#"
 3494        Line 0
 3495        Line 1
 3496        Line 2
 3497        Line 3
 3498        "#
 3499    .unindent();
 3500
 3501    cx.set_state(
 3502        &r#"
 3503        ˇLine 0
 3504        Line 1
 3505        Line 2
 3506        Line 3
 3507        "#
 3508        .unindent(),
 3509    );
 3510
 3511    cx.set_head_text(&diff_base);
 3512    executor.run_until_parked();
 3513
 3514    // Join lines
 3515    cx.update_editor(|editor, window, cx| {
 3516        editor.join_lines(&JoinLines, window, cx);
 3517    });
 3518    executor.run_until_parked();
 3519
 3520    cx.assert_editor_state(
 3521        &r#"
 3522        Line 0ˇ Line 1
 3523        Line 2
 3524        Line 3
 3525        "#
 3526        .unindent(),
 3527    );
 3528    // Join again
 3529    cx.update_editor(|editor, window, cx| {
 3530        editor.join_lines(&JoinLines, window, cx);
 3531    });
 3532    executor.run_until_parked();
 3533
 3534    cx.assert_editor_state(
 3535        &r#"
 3536        Line 0 Line 1ˇ Line 2
 3537        Line 3
 3538        "#
 3539        .unindent(),
 3540    );
 3541}
 3542
 3543#[gpui::test]
 3544async fn test_custom_newlines_cause_no_false_positive_diffs(
 3545    executor: BackgroundExecutor,
 3546    cx: &mut TestAppContext,
 3547) {
 3548    init_test(cx, |_| {});
 3549    let mut cx = EditorTestContext::new(cx).await;
 3550    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3551    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3552    executor.run_until_parked();
 3553
 3554    cx.update_editor(|editor, window, cx| {
 3555        let snapshot = editor.snapshot(window, cx);
 3556        assert_eq!(
 3557            snapshot
 3558                .buffer_snapshot
 3559                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3560                .collect::<Vec<_>>(),
 3561            Vec::new(),
 3562            "Should not have any diffs for files with custom newlines"
 3563        );
 3564    });
 3565}
 3566
 3567#[gpui::test]
 3568async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3569    init_test(cx, |_| {});
 3570
 3571    let mut cx = EditorTestContext::new(cx).await;
 3572
 3573    // Test sort_lines_case_insensitive()
 3574    cx.set_state(indoc! {"
 3575        «z
 3576        y
 3577        x
 3578        Z
 3579        Y
 3580        Xˇ»
 3581    "});
 3582    cx.update_editor(|e, window, cx| {
 3583        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3584    });
 3585    cx.assert_editor_state(indoc! {"
 3586        «x
 3587        X
 3588        y
 3589        Y
 3590        z
 3591        Zˇ»
 3592    "});
 3593
 3594    // Test reverse_lines()
 3595    cx.set_state(indoc! {"
 3596        «5
 3597        4
 3598        3
 3599        2
 3600        1ˇ»
 3601    "});
 3602    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3603    cx.assert_editor_state(indoc! {"
 3604        «1
 3605        2
 3606        3
 3607        4
 3608        5ˇ»
 3609    "});
 3610
 3611    // Skip testing shuffle_line()
 3612
 3613    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3614    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3615
 3616    // Don't manipulate when cursor is on single line, but expand the selection
 3617    cx.set_state(indoc! {"
 3618        ddˇdd
 3619        ccc
 3620        bb
 3621        a
 3622    "});
 3623    cx.update_editor(|e, window, cx| {
 3624        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3625    });
 3626    cx.assert_editor_state(indoc! {"
 3627        «ddddˇ»
 3628        ccc
 3629        bb
 3630        a
 3631    "});
 3632
 3633    // Basic manipulate case
 3634    // Start selection moves to column 0
 3635    // End of selection shrinks to fit shorter line
 3636    cx.set_state(indoc! {"
 3637        dd«d
 3638        ccc
 3639        bb
 3640        aaaaaˇ»
 3641    "});
 3642    cx.update_editor(|e, window, cx| {
 3643        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3644    });
 3645    cx.assert_editor_state(indoc! {"
 3646        «aaaaa
 3647        bb
 3648        ccc
 3649        dddˇ»
 3650    "});
 3651
 3652    // Manipulate case with newlines
 3653    cx.set_state(indoc! {"
 3654        dd«d
 3655        ccc
 3656
 3657        bb
 3658        aaaaa
 3659
 3660        ˇ»
 3661    "});
 3662    cx.update_editor(|e, window, cx| {
 3663        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3664    });
 3665    cx.assert_editor_state(indoc! {"
 3666        «
 3667
 3668        aaaaa
 3669        bb
 3670        ccc
 3671        dddˇ»
 3672
 3673    "});
 3674
 3675    // Adding new line
 3676    cx.set_state(indoc! {"
 3677        aa«a
 3678        bbˇ»b
 3679    "});
 3680    cx.update_editor(|e, window, cx| {
 3681        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 3682    });
 3683    cx.assert_editor_state(indoc! {"
 3684        «aaa
 3685        bbb
 3686        added_lineˇ»
 3687    "});
 3688
 3689    // Removing line
 3690    cx.set_state(indoc! {"
 3691        aa«a
 3692        bbbˇ»
 3693    "});
 3694    cx.update_editor(|e, window, cx| {
 3695        e.manipulate_lines(window, cx, |lines| {
 3696            lines.pop();
 3697        })
 3698    });
 3699    cx.assert_editor_state(indoc! {"
 3700        «aaaˇ»
 3701    "});
 3702
 3703    // Removing all lines
 3704    cx.set_state(indoc! {"
 3705        aa«a
 3706        bbbˇ»
 3707    "});
 3708    cx.update_editor(|e, window, cx| {
 3709        e.manipulate_lines(window, cx, |lines| {
 3710            lines.drain(..);
 3711        })
 3712    });
 3713    cx.assert_editor_state(indoc! {"
 3714        ˇ
 3715    "});
 3716}
 3717
 3718#[gpui::test]
 3719async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 3720    init_test(cx, |_| {});
 3721
 3722    let mut cx = EditorTestContext::new(cx).await;
 3723
 3724    // Consider continuous selection as single selection
 3725    cx.set_state(indoc! {"
 3726        Aaa«aa
 3727        cˇ»c«c
 3728        bb
 3729        aaaˇ»aa
 3730    "});
 3731    cx.update_editor(|e, window, cx| {
 3732        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3733    });
 3734    cx.assert_editor_state(indoc! {"
 3735        «Aaaaa
 3736        ccc
 3737        bb
 3738        aaaaaˇ»
 3739    "});
 3740
 3741    cx.set_state(indoc! {"
 3742        Aaa«aa
 3743        cˇ»c«c
 3744        bb
 3745        aaaˇ»aa
 3746    "});
 3747    cx.update_editor(|e, window, cx| {
 3748        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3749    });
 3750    cx.assert_editor_state(indoc! {"
 3751        «Aaaaa
 3752        ccc
 3753        bbˇ»
 3754    "});
 3755
 3756    // Consider non continuous selection as distinct dedup operations
 3757    cx.set_state(indoc! {"
 3758        «aaaaa
 3759        bb
 3760        aaaaa
 3761        aaaaaˇ»
 3762
 3763        aaa«aaˇ»
 3764    "});
 3765    cx.update_editor(|e, window, cx| {
 3766        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3767    });
 3768    cx.assert_editor_state(indoc! {"
 3769        «aaaaa
 3770        bbˇ»
 3771
 3772        «aaaaaˇ»
 3773    "});
 3774}
 3775
 3776#[gpui::test]
 3777async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 3778    init_test(cx, |_| {});
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781
 3782    cx.set_state(indoc! {"
 3783        «Aaa
 3784        aAa
 3785        Aaaˇ»
 3786    "});
 3787    cx.update_editor(|e, window, cx| {
 3788        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3789    });
 3790    cx.assert_editor_state(indoc! {"
 3791        «Aaa
 3792        aAaˇ»
 3793    "});
 3794
 3795    cx.set_state(indoc! {"
 3796        «Aaa
 3797        aAa
 3798        aaAˇ»
 3799    "});
 3800    cx.update_editor(|e, window, cx| {
 3801        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3802    });
 3803    cx.assert_editor_state(indoc! {"
 3804        «Aaaˇ»
 3805    "});
 3806}
 3807
 3808#[gpui::test]
 3809async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 3810    init_test(cx, |_| {});
 3811
 3812    let mut cx = EditorTestContext::new(cx).await;
 3813
 3814    // Manipulate with multiple selections on a single line
 3815    cx.set_state(indoc! {"
 3816        dd«dd
 3817        cˇ»c«c
 3818        bb
 3819        aaaˇ»aa
 3820    "});
 3821    cx.update_editor(|e, window, cx| {
 3822        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3823    });
 3824    cx.assert_editor_state(indoc! {"
 3825        «aaaaa
 3826        bb
 3827        ccc
 3828        ddddˇ»
 3829    "});
 3830
 3831    // Manipulate with multiple disjoin selections
 3832    cx.set_state(indoc! {"
 3833 3834        4
 3835        3
 3836        2
 3837        1ˇ»
 3838
 3839        dd«dd
 3840        ccc
 3841        bb
 3842        aaaˇ»aa
 3843    "});
 3844    cx.update_editor(|e, window, cx| {
 3845        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3846    });
 3847    cx.assert_editor_state(indoc! {"
 3848        «1
 3849        2
 3850        3
 3851        4
 3852        5ˇ»
 3853
 3854        «aaaaa
 3855        bb
 3856        ccc
 3857        ddddˇ»
 3858    "});
 3859
 3860    // Adding lines on each selection
 3861    cx.set_state(indoc! {"
 3862 3863        1ˇ»
 3864
 3865        bb«bb
 3866        aaaˇ»aa
 3867    "});
 3868    cx.update_editor(|e, window, cx| {
 3869        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 3870    });
 3871    cx.assert_editor_state(indoc! {"
 3872        «2
 3873        1
 3874        added lineˇ»
 3875
 3876        «bbbb
 3877        aaaaa
 3878        added lineˇ»
 3879    "});
 3880
 3881    // Removing lines on each selection
 3882    cx.set_state(indoc! {"
 3883 3884        1ˇ»
 3885
 3886        bb«bb
 3887        aaaˇ»aa
 3888    "});
 3889    cx.update_editor(|e, window, cx| {
 3890        e.manipulate_lines(window, cx, |lines| {
 3891            lines.pop();
 3892        })
 3893    });
 3894    cx.assert_editor_state(indoc! {"
 3895        «2ˇ»
 3896
 3897        «bbbbˇ»
 3898    "});
 3899}
 3900
 3901#[gpui::test]
 3902async fn test_toggle_case(cx: &mut TestAppContext) {
 3903    init_test(cx, |_| {});
 3904
 3905    let mut cx = EditorTestContext::new(cx).await;
 3906
 3907    // If all lower case -> upper case
 3908    cx.set_state(indoc! {"
 3909        «hello worldˇ»
 3910    "});
 3911    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3912    cx.assert_editor_state(indoc! {"
 3913        «HELLO WORLDˇ»
 3914    "});
 3915
 3916    // If all upper case -> lower case
 3917    cx.set_state(indoc! {"
 3918        «HELLO WORLDˇ»
 3919    "});
 3920    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3921    cx.assert_editor_state(indoc! {"
 3922        «hello worldˇ»
 3923    "});
 3924
 3925    // If any upper case characters are identified -> lower case
 3926    // This matches JetBrains IDEs
 3927    cx.set_state(indoc! {"
 3928        «hEllo worldˇ»
 3929    "});
 3930    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3931    cx.assert_editor_state(indoc! {"
 3932        «hello worldˇ»
 3933    "});
 3934}
 3935
 3936#[gpui::test]
 3937async fn test_manipulate_text(cx: &mut TestAppContext) {
 3938    init_test(cx, |_| {});
 3939
 3940    let mut cx = EditorTestContext::new(cx).await;
 3941
 3942    // Test convert_to_upper_case()
 3943    cx.set_state(indoc! {"
 3944        «hello worldˇ»
 3945    "});
 3946    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3947    cx.assert_editor_state(indoc! {"
 3948        «HELLO WORLDˇ»
 3949    "});
 3950
 3951    // Test convert_to_lower_case()
 3952    cx.set_state(indoc! {"
 3953        «HELLO WORLDˇ»
 3954    "});
 3955    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 3956    cx.assert_editor_state(indoc! {"
 3957        «hello worldˇ»
 3958    "});
 3959
 3960    // Test multiple line, single selection case
 3961    cx.set_state(indoc! {"
 3962        «The quick brown
 3963        fox jumps over
 3964        the lazy dogˇ»
 3965    "});
 3966    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 3967    cx.assert_editor_state(indoc! {"
 3968        «The Quick Brown
 3969        Fox Jumps Over
 3970        The Lazy Dogˇ»
 3971    "});
 3972
 3973    // Test multiple line, single selection case
 3974    cx.set_state(indoc! {"
 3975        «The quick brown
 3976        fox jumps over
 3977        the lazy dogˇ»
 3978    "});
 3979    cx.update_editor(|e, window, cx| {
 3980        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 3981    });
 3982    cx.assert_editor_state(indoc! {"
 3983        «TheQuickBrown
 3984        FoxJumpsOver
 3985        TheLazyDogˇ»
 3986    "});
 3987
 3988    // From here on out, test more complex cases of manipulate_text()
 3989
 3990    // Test no selection case - should affect words cursors are in
 3991    // Cursor at beginning, middle, and end of word
 3992    cx.set_state(indoc! {"
 3993        ˇhello big beauˇtiful worldˇ
 3994    "});
 3995    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3996    cx.assert_editor_state(indoc! {"
 3997        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 3998    "});
 3999
 4000    // Test multiple selections on a single line and across multiple lines
 4001    cx.set_state(indoc! {"
 4002        «Theˇ» quick «brown
 4003        foxˇ» jumps «overˇ»
 4004        the «lazyˇ» dog
 4005    "});
 4006    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4007    cx.assert_editor_state(indoc! {"
 4008        «THEˇ» quick «BROWN
 4009        FOXˇ» jumps «OVERˇ»
 4010        the «LAZYˇ» dog
 4011    "});
 4012
 4013    // Test case where text length grows
 4014    cx.set_state(indoc! {"
 4015        «tschüߡ»
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        «TSCHÜSSˇ»
 4020    "});
 4021
 4022    // Test to make sure we don't crash when text shrinks
 4023    cx.set_state(indoc! {"
 4024        aaa_bbbˇ
 4025    "});
 4026    cx.update_editor(|e, window, cx| {
 4027        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4028    });
 4029    cx.assert_editor_state(indoc! {"
 4030        «aaaBbbˇ»
 4031    "});
 4032
 4033    // Test to make sure we all aware of the fact that each word can grow and shrink
 4034    // Final selections should be aware of this fact
 4035    cx.set_state(indoc! {"
 4036        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4037    "});
 4038    cx.update_editor(|e, window, cx| {
 4039        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4040    });
 4041    cx.assert_editor_state(indoc! {"
 4042        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4043    "});
 4044
 4045    cx.set_state(indoc! {"
 4046        «hElLo, WoRld!ˇ»
 4047    "});
 4048    cx.update_editor(|e, window, cx| {
 4049        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4050    });
 4051    cx.assert_editor_state(indoc! {"
 4052        «HeLlO, wOrLD!ˇ»
 4053    "});
 4054}
 4055
 4056#[gpui::test]
 4057fn test_duplicate_line(cx: &mut TestAppContext) {
 4058    init_test(cx, |_| {});
 4059
 4060    let editor = cx.add_window(|window, cx| {
 4061        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4062        build_editor(buffer, window, cx)
 4063    });
 4064    _ = editor.update(cx, |editor, window, cx| {
 4065        editor.change_selections(None, window, cx, |s| {
 4066            s.select_display_ranges([
 4067                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4068                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4069                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4070                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4071            ])
 4072        });
 4073        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4074        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4075        assert_eq!(
 4076            editor.selections.display_ranges(cx),
 4077            vec![
 4078                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4079                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4080                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4081                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4082            ]
 4083        );
 4084    });
 4085
 4086    let editor = cx.add_window(|window, cx| {
 4087        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4088        build_editor(buffer, window, cx)
 4089    });
 4090    _ = editor.update(cx, |editor, window, cx| {
 4091        editor.change_selections(None, window, cx, |s| {
 4092            s.select_display_ranges([
 4093                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4094                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4095            ])
 4096        });
 4097        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4098        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4099        assert_eq!(
 4100            editor.selections.display_ranges(cx),
 4101            vec![
 4102                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4103                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4104            ]
 4105        );
 4106    });
 4107
 4108    // With `move_upwards` the selections stay in place, except for
 4109    // the lines inserted above them
 4110    let editor = cx.add_window(|window, cx| {
 4111        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4112        build_editor(buffer, window, cx)
 4113    });
 4114    _ = editor.update(cx, |editor, window, cx| {
 4115        editor.change_selections(None, window, cx, |s| {
 4116            s.select_display_ranges([
 4117                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4118                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4119                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4120                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4121            ])
 4122        });
 4123        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4124        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4125        assert_eq!(
 4126            editor.selections.display_ranges(cx),
 4127            vec![
 4128                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4129                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4130                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4131                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4132            ]
 4133        );
 4134    });
 4135
 4136    let editor = cx.add_window(|window, cx| {
 4137        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4138        build_editor(buffer, window, cx)
 4139    });
 4140    _ = editor.update(cx, |editor, window, cx| {
 4141        editor.change_selections(None, window, cx, |s| {
 4142            s.select_display_ranges([
 4143                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4144                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4145            ])
 4146        });
 4147        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4148        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4149        assert_eq!(
 4150            editor.selections.display_ranges(cx),
 4151            vec![
 4152                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4153                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4154            ]
 4155        );
 4156    });
 4157
 4158    let editor = cx.add_window(|window, cx| {
 4159        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4160        build_editor(buffer, window, cx)
 4161    });
 4162    _ = editor.update(cx, |editor, window, cx| {
 4163        editor.change_selections(None, window, cx, |s| {
 4164            s.select_display_ranges([
 4165                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4166                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4167            ])
 4168        });
 4169        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4170        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4171        assert_eq!(
 4172            editor.selections.display_ranges(cx),
 4173            vec![
 4174                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4175                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4176            ]
 4177        );
 4178    });
 4179}
 4180
 4181#[gpui::test]
 4182fn test_move_line_up_down(cx: &mut TestAppContext) {
 4183    init_test(cx, |_| {});
 4184
 4185    let editor = cx.add_window(|window, cx| {
 4186        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4187        build_editor(buffer, window, cx)
 4188    });
 4189    _ = editor.update(cx, |editor, window, cx| {
 4190        editor.fold_creases(
 4191            vec![
 4192                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4193                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4194                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4195            ],
 4196            true,
 4197            window,
 4198            cx,
 4199        );
 4200        editor.change_selections(None, window, cx, |s| {
 4201            s.select_display_ranges([
 4202                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4203                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4204                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4205                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4206            ])
 4207        });
 4208        assert_eq!(
 4209            editor.display_text(cx),
 4210            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4211        );
 4212
 4213        editor.move_line_up(&MoveLineUp, window, cx);
 4214        assert_eq!(
 4215            editor.display_text(cx),
 4216            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4217        );
 4218        assert_eq!(
 4219            editor.selections.display_ranges(cx),
 4220            vec![
 4221                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4222                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4223                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4224                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4225            ]
 4226        );
 4227    });
 4228
 4229    _ = editor.update(cx, |editor, window, cx| {
 4230        editor.move_line_down(&MoveLineDown, window, cx);
 4231        assert_eq!(
 4232            editor.display_text(cx),
 4233            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4234        );
 4235        assert_eq!(
 4236            editor.selections.display_ranges(cx),
 4237            vec![
 4238                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4239                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4240                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4241                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4242            ]
 4243        );
 4244    });
 4245
 4246    _ = editor.update(cx, |editor, window, cx| {
 4247        editor.move_line_down(&MoveLineDown, window, cx);
 4248        assert_eq!(
 4249            editor.display_text(cx),
 4250            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4251        );
 4252        assert_eq!(
 4253            editor.selections.display_ranges(cx),
 4254            vec![
 4255                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4256                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4257                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4258                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4259            ]
 4260        );
 4261    });
 4262
 4263    _ = editor.update(cx, |editor, window, cx| {
 4264        editor.move_line_up(&MoveLineUp, window, cx);
 4265        assert_eq!(
 4266            editor.display_text(cx),
 4267            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4268        );
 4269        assert_eq!(
 4270            editor.selections.display_ranges(cx),
 4271            vec![
 4272                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4273                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4274                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4275                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4276            ]
 4277        );
 4278    });
 4279}
 4280
 4281#[gpui::test]
 4282fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4283    init_test(cx, |_| {});
 4284
 4285    let editor = cx.add_window(|window, cx| {
 4286        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4287        build_editor(buffer, window, cx)
 4288    });
 4289    _ = editor.update(cx, |editor, window, cx| {
 4290        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4291        editor.insert_blocks(
 4292            [BlockProperties {
 4293                style: BlockStyle::Fixed,
 4294                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4295                height: Some(1),
 4296                render: Arc::new(|_| div().into_any()),
 4297                priority: 0,
 4298            }],
 4299            Some(Autoscroll::fit()),
 4300            cx,
 4301        );
 4302        editor.change_selections(None, window, cx, |s| {
 4303            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4304        });
 4305        editor.move_line_down(&MoveLineDown, window, cx);
 4306    });
 4307}
 4308
 4309#[gpui::test]
 4310async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4311    init_test(cx, |_| {});
 4312
 4313    let mut cx = EditorTestContext::new(cx).await;
 4314    cx.set_state(
 4315        &"
 4316            ˇzero
 4317            one
 4318            two
 4319            three
 4320            four
 4321            five
 4322        "
 4323        .unindent(),
 4324    );
 4325
 4326    // Create a four-line block that replaces three lines of text.
 4327    cx.update_editor(|editor, window, cx| {
 4328        let snapshot = editor.snapshot(window, cx);
 4329        let snapshot = &snapshot.buffer_snapshot;
 4330        let placement = BlockPlacement::Replace(
 4331            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4332        );
 4333        editor.insert_blocks(
 4334            [BlockProperties {
 4335                placement,
 4336                height: Some(4),
 4337                style: BlockStyle::Sticky,
 4338                render: Arc::new(|_| gpui::div().into_any_element()),
 4339                priority: 0,
 4340            }],
 4341            None,
 4342            cx,
 4343        );
 4344    });
 4345
 4346    // Move down so that the cursor touches the block.
 4347    cx.update_editor(|editor, window, cx| {
 4348        editor.move_down(&Default::default(), window, cx);
 4349    });
 4350    cx.assert_editor_state(
 4351        &"
 4352            zero
 4353            «one
 4354            two
 4355            threeˇ»
 4356            four
 4357            five
 4358        "
 4359        .unindent(),
 4360    );
 4361
 4362    // Move down past the block.
 4363    cx.update_editor(|editor, window, cx| {
 4364        editor.move_down(&Default::default(), window, cx);
 4365    });
 4366    cx.assert_editor_state(
 4367        &"
 4368            zero
 4369            one
 4370            two
 4371            three
 4372            ˇfour
 4373            five
 4374        "
 4375        .unindent(),
 4376    );
 4377}
 4378
 4379#[gpui::test]
 4380fn test_transpose(cx: &mut TestAppContext) {
 4381    init_test(cx, |_| {});
 4382
 4383    _ = cx.add_window(|window, cx| {
 4384        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4385        editor.set_style(EditorStyle::default(), window, cx);
 4386        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4387        editor.transpose(&Default::default(), window, cx);
 4388        assert_eq!(editor.text(cx), "bac");
 4389        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4390
 4391        editor.transpose(&Default::default(), window, cx);
 4392        assert_eq!(editor.text(cx), "bca");
 4393        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4394
 4395        editor.transpose(&Default::default(), window, cx);
 4396        assert_eq!(editor.text(cx), "bac");
 4397        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4398
 4399        editor
 4400    });
 4401
 4402    _ = cx.add_window(|window, cx| {
 4403        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4404        editor.set_style(EditorStyle::default(), window, cx);
 4405        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4406        editor.transpose(&Default::default(), window, cx);
 4407        assert_eq!(editor.text(cx), "acb\nde");
 4408        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4409
 4410        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4411        editor.transpose(&Default::default(), window, cx);
 4412        assert_eq!(editor.text(cx), "acbd\ne");
 4413        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4414
 4415        editor.transpose(&Default::default(), window, cx);
 4416        assert_eq!(editor.text(cx), "acbde\n");
 4417        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4418
 4419        editor.transpose(&Default::default(), window, cx);
 4420        assert_eq!(editor.text(cx), "acbd\ne");
 4421        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4422
 4423        editor
 4424    });
 4425
 4426    _ = cx.add_window(|window, cx| {
 4427        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4428        editor.set_style(EditorStyle::default(), window, cx);
 4429        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4430        editor.transpose(&Default::default(), window, cx);
 4431        assert_eq!(editor.text(cx), "bacd\ne");
 4432        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4433
 4434        editor.transpose(&Default::default(), window, cx);
 4435        assert_eq!(editor.text(cx), "bcade\n");
 4436        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4437
 4438        editor.transpose(&Default::default(), window, cx);
 4439        assert_eq!(editor.text(cx), "bcda\ne");
 4440        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4441
 4442        editor.transpose(&Default::default(), window, cx);
 4443        assert_eq!(editor.text(cx), "bcade\n");
 4444        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4445
 4446        editor.transpose(&Default::default(), window, cx);
 4447        assert_eq!(editor.text(cx), "bcaed\n");
 4448        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4449
 4450        editor
 4451    });
 4452
 4453    _ = cx.add_window(|window, cx| {
 4454        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4455        editor.set_style(EditorStyle::default(), window, cx);
 4456        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4457        editor.transpose(&Default::default(), window, cx);
 4458        assert_eq!(editor.text(cx), "🏀🍐✋");
 4459        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4460
 4461        editor.transpose(&Default::default(), window, cx);
 4462        assert_eq!(editor.text(cx), "🏀✋🍐");
 4463        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4464
 4465        editor.transpose(&Default::default(), window, cx);
 4466        assert_eq!(editor.text(cx), "🏀🍐✋");
 4467        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4468
 4469        editor
 4470    });
 4471}
 4472
 4473#[gpui::test]
 4474async fn test_rewrap(cx: &mut TestAppContext) {
 4475    init_test(cx, |settings| {
 4476        settings.languages.extend([
 4477            (
 4478                "Markdown".into(),
 4479                LanguageSettingsContent {
 4480                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4481                    ..Default::default()
 4482                },
 4483            ),
 4484            (
 4485                "Plain Text".into(),
 4486                LanguageSettingsContent {
 4487                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4488                    ..Default::default()
 4489                },
 4490            ),
 4491        ])
 4492    });
 4493
 4494    let mut cx = EditorTestContext::new(cx).await;
 4495
 4496    let language_with_c_comments = Arc::new(Language::new(
 4497        LanguageConfig {
 4498            line_comments: vec!["// ".into()],
 4499            ..LanguageConfig::default()
 4500        },
 4501        None,
 4502    ));
 4503    let language_with_pound_comments = Arc::new(Language::new(
 4504        LanguageConfig {
 4505            line_comments: vec!["# ".into()],
 4506            ..LanguageConfig::default()
 4507        },
 4508        None,
 4509    ));
 4510    let markdown_language = Arc::new(Language::new(
 4511        LanguageConfig {
 4512            name: "Markdown".into(),
 4513            ..LanguageConfig::default()
 4514        },
 4515        None,
 4516    ));
 4517    let language_with_doc_comments = Arc::new(Language::new(
 4518        LanguageConfig {
 4519            line_comments: vec!["// ".into(), "/// ".into()],
 4520            ..LanguageConfig::default()
 4521        },
 4522        Some(tree_sitter_rust::LANGUAGE.into()),
 4523    ));
 4524
 4525    let plaintext_language = Arc::new(Language::new(
 4526        LanguageConfig {
 4527            name: "Plain Text".into(),
 4528            ..LanguageConfig::default()
 4529        },
 4530        None,
 4531    ));
 4532
 4533    assert_rewrap(
 4534        indoc! {"
 4535            // ˇ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.
 4536        "},
 4537        indoc! {"
 4538            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4539            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4540            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4541            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4542            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4543            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4544            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4545            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4546            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4547            // porttitor id. Aliquam id accumsan eros.
 4548        "},
 4549        language_with_c_comments.clone(),
 4550        &mut cx,
 4551    );
 4552
 4553    // Test that rewrapping works inside of a selection
 4554    assert_rewrap(
 4555        indoc! {"
 4556            «// 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.ˇ»
 4557        "},
 4558        indoc! {"
 4559            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4560            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4561            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4562            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4563            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4564            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4565            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4566            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4567            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4568            // porttitor id. Aliquam id accumsan eros.ˇ»
 4569        "},
 4570        language_with_c_comments.clone(),
 4571        &mut cx,
 4572    );
 4573
 4574    // Test that cursors that expand to the same region are collapsed.
 4575    assert_rewrap(
 4576        indoc! {"
 4577            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4578            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4579            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4580            // ˇ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.
 4581        "},
 4582        indoc! {"
 4583            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4584            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4585            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4586            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4587            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4588            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4589            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4590            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4591            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4592            // porttitor id. Aliquam id accumsan eros.
 4593        "},
 4594        language_with_c_comments.clone(),
 4595        &mut cx,
 4596    );
 4597
 4598    // Test that non-contiguous selections are treated separately.
 4599    assert_rewrap(
 4600        indoc! {"
 4601            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4602            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4603            //
 4604            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4605            // ˇ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.
 4606        "},
 4607        indoc! {"
 4608            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4609            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4610            // auctor, eu lacinia sapien scelerisque.
 4611            //
 4612            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 4613            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4614            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 4615            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 4616            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 4617            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 4618            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 4619        "},
 4620        language_with_c_comments.clone(),
 4621        &mut cx,
 4622    );
 4623
 4624    // Test that different comment prefixes are supported.
 4625    assert_rewrap(
 4626        indoc! {"
 4627            # ˇ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.
 4628        "},
 4629        indoc! {"
 4630            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4631            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4632            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4633            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4634            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 4635            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 4636            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 4637            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 4638            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 4639            # accumsan eros.
 4640        "},
 4641        language_with_pound_comments.clone(),
 4642        &mut cx,
 4643    );
 4644
 4645    // Test that rewrapping is ignored outside of comments in most languages.
 4646    assert_rewrap(
 4647        indoc! {"
 4648            /// Adds two numbers.
 4649            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4650            fn add(a: u32, b: u32) -> u32 {
 4651                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ˇ
 4652            }
 4653        "},
 4654        indoc! {"
 4655            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 4656            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4657            fn add(a: u32, b: u32) -> u32 {
 4658                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ˇ
 4659            }
 4660        "},
 4661        language_with_doc_comments.clone(),
 4662        &mut cx,
 4663    );
 4664
 4665    // Test that rewrapping works in Markdown and Plain Text languages.
 4666    assert_rewrap(
 4667        indoc! {"
 4668            # Hello
 4669
 4670            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.
 4671        "},
 4672        indoc! {"
 4673            # Hello
 4674
 4675            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4676            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4677            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4678            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4679            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4680            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4681            Integer sit amet scelerisque nisi.
 4682        "},
 4683        markdown_language,
 4684        &mut cx,
 4685    );
 4686
 4687    assert_rewrap(
 4688        indoc! {"
 4689            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.
 4690        "},
 4691        indoc! {"
 4692            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4693            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4694            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4695            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4696            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4697            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4698            Integer sit amet scelerisque nisi.
 4699        "},
 4700        plaintext_language,
 4701        &mut cx,
 4702    );
 4703
 4704    // Test rewrapping unaligned comments in a selection.
 4705    assert_rewrap(
 4706        indoc! {"
 4707            fn foo() {
 4708                if true {
 4709            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4710            // Praesent semper egestas tellus id dignissim.ˇ»
 4711                    do_something();
 4712                } else {
 4713                    //
 4714                }
 4715            }
 4716        "},
 4717        indoc! {"
 4718            fn foo() {
 4719                if true {
 4720            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4721                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4722                    // egestas tellus id dignissim.ˇ»
 4723                    do_something();
 4724                } else {
 4725                    //
 4726                }
 4727            }
 4728        "},
 4729        language_with_doc_comments.clone(),
 4730        &mut cx,
 4731    );
 4732
 4733    assert_rewrap(
 4734        indoc! {"
 4735            fn foo() {
 4736                if true {
 4737            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4738            // Praesent semper egestas tellus id dignissim.»
 4739                    do_something();
 4740                } else {
 4741                    //
 4742                }
 4743
 4744            }
 4745        "},
 4746        indoc! {"
 4747            fn foo() {
 4748                if true {
 4749            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4750                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4751                    // egestas tellus id dignissim.»
 4752                    do_something();
 4753                } else {
 4754                    //
 4755                }
 4756
 4757            }
 4758        "},
 4759        language_with_doc_comments.clone(),
 4760        &mut cx,
 4761    );
 4762
 4763    #[track_caller]
 4764    fn assert_rewrap(
 4765        unwrapped_text: &str,
 4766        wrapped_text: &str,
 4767        language: Arc<Language>,
 4768        cx: &mut EditorTestContext,
 4769    ) {
 4770        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4771        cx.set_state(unwrapped_text);
 4772        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 4773        cx.assert_editor_state(wrapped_text);
 4774    }
 4775}
 4776
 4777#[gpui::test]
 4778async fn test_hard_wrap(cx: &mut TestAppContext) {
 4779    init_test(cx, |_| {});
 4780    let mut cx = EditorTestContext::new(cx).await;
 4781
 4782    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 4783    cx.update_editor(|editor, _, cx| {
 4784        editor.set_hard_wrap(Some(14), cx);
 4785    });
 4786
 4787    cx.set_state(indoc!(
 4788        "
 4789        one two three ˇ
 4790        "
 4791    ));
 4792    cx.simulate_input("four");
 4793    cx.run_until_parked();
 4794
 4795    cx.assert_editor_state(indoc!(
 4796        "
 4797        one two three
 4798        fourˇ
 4799        "
 4800    ));
 4801
 4802    cx.update_editor(|editor, window, cx| {
 4803        editor.newline(&Default::default(), window, cx);
 4804    });
 4805    cx.run_until_parked();
 4806    cx.assert_editor_state(indoc!(
 4807        "
 4808        one two three
 4809        four
 4810        ˇ
 4811        "
 4812    ));
 4813
 4814    cx.simulate_input("five");
 4815    cx.run_until_parked();
 4816    cx.assert_editor_state(indoc!(
 4817        "
 4818        one two three
 4819        four
 4820        fiveˇ
 4821        "
 4822    ));
 4823
 4824    cx.update_editor(|editor, window, cx| {
 4825        editor.newline(&Default::default(), window, cx);
 4826    });
 4827    cx.run_until_parked();
 4828    cx.simulate_input("# ");
 4829    cx.run_until_parked();
 4830    cx.assert_editor_state(indoc!(
 4831        "
 4832        one two three
 4833        four
 4834        five
 4835        # ˇ
 4836        "
 4837    ));
 4838
 4839    cx.update_editor(|editor, window, cx| {
 4840        editor.newline(&Default::default(), window, cx);
 4841    });
 4842    cx.run_until_parked();
 4843    cx.assert_editor_state(indoc!(
 4844        "
 4845        one two three
 4846        four
 4847        five
 4848        #\x20
 4849 4850        "
 4851    ));
 4852
 4853    cx.simulate_input(" 6");
 4854    cx.run_until_parked();
 4855    cx.assert_editor_state(indoc!(
 4856        "
 4857        one two three
 4858        four
 4859        five
 4860        #
 4861        # 6ˇ
 4862        "
 4863    ));
 4864}
 4865
 4866#[gpui::test]
 4867async fn test_clipboard(cx: &mut TestAppContext) {
 4868    init_test(cx, |_| {});
 4869
 4870    let mut cx = EditorTestContext::new(cx).await;
 4871
 4872    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 4873    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4874    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 4875
 4876    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 4877    cx.set_state("two ˇfour ˇsix ˇ");
 4878    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4879    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 4880
 4881    // Paste again but with only two cursors. Since the number of cursors doesn't
 4882    // match the number of slices in the clipboard, the entire clipboard text
 4883    // is pasted at each cursor.
 4884    cx.set_state("ˇtwo one✅ four three six five ˇ");
 4885    cx.update_editor(|e, window, cx| {
 4886        e.handle_input("( ", window, cx);
 4887        e.paste(&Paste, window, cx);
 4888        e.handle_input(") ", window, cx);
 4889    });
 4890    cx.assert_editor_state(
 4891        &([
 4892            "( one✅ ",
 4893            "three ",
 4894            "five ) ˇtwo one✅ four three six five ( one✅ ",
 4895            "three ",
 4896            "five ) ˇ",
 4897        ]
 4898        .join("\n")),
 4899    );
 4900
 4901    // Cut with three selections, one of which is full-line.
 4902    cx.set_state(indoc! {"
 4903        1«2ˇ»3
 4904        4ˇ567
 4905        «8ˇ»9"});
 4906    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4907    cx.assert_editor_state(indoc! {"
 4908        1ˇ3
 4909        ˇ9"});
 4910
 4911    // Paste with three selections, noticing how the copied selection that was full-line
 4912    // gets inserted before the second cursor.
 4913    cx.set_state(indoc! {"
 4914        1ˇ3
 4915 4916        «oˇ»ne"});
 4917    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4918    cx.assert_editor_state(indoc! {"
 4919        12ˇ3
 4920        4567
 4921 4922        8ˇne"});
 4923
 4924    // Copy with a single cursor only, which writes the whole line into the clipboard.
 4925    cx.set_state(indoc! {"
 4926        The quick brown
 4927        fox juˇmps over
 4928        the lazy dog"});
 4929    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4930    assert_eq!(
 4931        cx.read_from_clipboard()
 4932            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4933        Some("fox jumps over\n".to_string())
 4934    );
 4935
 4936    // Paste with three selections, noticing how the copied full-line selection is inserted
 4937    // before the empty selections but replaces the selection that is non-empty.
 4938    cx.set_state(indoc! {"
 4939        Tˇhe quick brown
 4940        «foˇ»x jumps over
 4941        tˇhe lazy dog"});
 4942    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4943    cx.assert_editor_state(indoc! {"
 4944        fox jumps over
 4945        Tˇhe quick brown
 4946        fox jumps over
 4947        ˇx jumps over
 4948        fox jumps over
 4949        tˇhe lazy dog"});
 4950}
 4951
 4952#[gpui::test]
 4953async fn test_copy_trim(cx: &mut TestAppContext) {
 4954    init_test(cx, |_| {});
 4955
 4956    let mut cx = EditorTestContext::new(cx).await;
 4957    cx.set_state(
 4958        r#"            «for selection in selections.iter() {
 4959            let mut start = selection.start;
 4960            let mut end = selection.end;
 4961            let is_entire_line = selection.is_empty();
 4962            if is_entire_line {
 4963                start = Point::new(start.row, 0);ˇ»
 4964                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 4965            }
 4966        "#,
 4967    );
 4968    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4969    assert_eq!(
 4970        cx.read_from_clipboard()
 4971            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4972        Some(
 4973            "for selection in selections.iter() {
 4974            let mut start = selection.start;
 4975            let mut end = selection.end;
 4976            let is_entire_line = selection.is_empty();
 4977            if is_entire_line {
 4978                start = Point::new(start.row, 0);"
 4979                .to_string()
 4980        ),
 4981        "Regular copying preserves all indentation selected",
 4982    );
 4983    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 4984    assert_eq!(
 4985        cx.read_from_clipboard()
 4986            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4987        Some(
 4988            "for selection in selections.iter() {
 4989let mut start = selection.start;
 4990let mut end = selection.end;
 4991let is_entire_line = selection.is_empty();
 4992if is_entire_line {
 4993    start = Point::new(start.row, 0);"
 4994                .to_string()
 4995        ),
 4996        "Copying with stripping should strip all leading whitespaces"
 4997    );
 4998
 4999    cx.set_state(
 5000        r#"       «     for selection in selections.iter() {
 5001            let mut start = selection.start;
 5002            let mut end = selection.end;
 5003            let is_entire_line = selection.is_empty();
 5004            if is_entire_line {
 5005                start = Point::new(start.row, 0);ˇ»
 5006                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5007            }
 5008        "#,
 5009    );
 5010    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5011    assert_eq!(
 5012        cx.read_from_clipboard()
 5013            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5014        Some(
 5015            "     for selection in selections.iter() {
 5016            let mut start = selection.start;
 5017            let mut end = selection.end;
 5018            let is_entire_line = selection.is_empty();
 5019            if is_entire_line {
 5020                start = Point::new(start.row, 0);"
 5021                .to_string()
 5022        ),
 5023        "Regular copying preserves all indentation selected",
 5024    );
 5025    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5026    assert_eq!(
 5027        cx.read_from_clipboard()
 5028            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5029        Some(
 5030            "for selection in selections.iter() {
 5031let mut start = selection.start;
 5032let mut end = selection.end;
 5033let is_entire_line = selection.is_empty();
 5034if is_entire_line {
 5035    start = Point::new(start.row, 0);"
 5036                .to_string()
 5037        ),
 5038        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5039    );
 5040
 5041    cx.set_state(
 5042        r#"       «ˇ     for selection in selections.iter() {
 5043            let mut start = selection.start;
 5044            let mut end = selection.end;
 5045            let is_entire_line = selection.is_empty();
 5046            if is_entire_line {
 5047                start = Point::new(start.row, 0);»
 5048                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5049            }
 5050        "#,
 5051    );
 5052    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5053    assert_eq!(
 5054        cx.read_from_clipboard()
 5055            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5056        Some(
 5057            "     for selection in selections.iter() {
 5058            let mut start = selection.start;
 5059            let mut end = selection.end;
 5060            let is_entire_line = selection.is_empty();
 5061            if is_entire_line {
 5062                start = Point::new(start.row, 0);"
 5063                .to_string()
 5064        ),
 5065        "Regular copying for reverse selection works the same",
 5066    );
 5067    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5068    assert_eq!(
 5069        cx.read_from_clipboard()
 5070            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5071        Some(
 5072            "for selection in selections.iter() {
 5073let mut start = selection.start;
 5074let mut end = selection.end;
 5075let is_entire_line = selection.is_empty();
 5076if is_entire_line {
 5077    start = Point::new(start.row, 0);"
 5078                .to_string()
 5079        ),
 5080        "Copying with stripping for reverse selection works the same"
 5081    );
 5082
 5083    cx.set_state(
 5084        r#"            for selection «in selections.iter() {
 5085            let mut start = selection.start;
 5086            let mut end = selection.end;
 5087            let is_entire_line = selection.is_empty();
 5088            if is_entire_line {
 5089                start = Point::new(start.row, 0);ˇ»
 5090                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5091            }
 5092        "#,
 5093    );
 5094    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5095    assert_eq!(
 5096        cx.read_from_clipboard()
 5097            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5098        Some(
 5099            "in selections.iter() {
 5100            let mut start = selection.start;
 5101            let mut end = selection.end;
 5102            let is_entire_line = selection.is_empty();
 5103            if is_entire_line {
 5104                start = Point::new(start.row, 0);"
 5105                .to_string()
 5106        ),
 5107        "When selecting past the indent, the copying works as usual",
 5108    );
 5109    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5110    assert_eq!(
 5111        cx.read_from_clipboard()
 5112            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5113        Some(
 5114            "in selections.iter() {
 5115            let mut start = selection.start;
 5116            let mut end = selection.end;
 5117            let is_entire_line = selection.is_empty();
 5118            if is_entire_line {
 5119                start = Point::new(start.row, 0);"
 5120                .to_string()
 5121        ),
 5122        "When selecting past the indent, nothing is trimmed"
 5123    );
 5124}
 5125
 5126#[gpui::test]
 5127async fn test_paste_multiline(cx: &mut TestAppContext) {
 5128    init_test(cx, |_| {});
 5129
 5130    let mut cx = EditorTestContext::new(cx).await;
 5131    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5132
 5133    // Cut an indented block, without the leading whitespace.
 5134    cx.set_state(indoc! {"
 5135        const a: B = (
 5136            c(),
 5137            «d(
 5138                e,
 5139                f
 5140            )ˇ»
 5141        );
 5142    "});
 5143    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5144    cx.assert_editor_state(indoc! {"
 5145        const a: B = (
 5146            c(),
 5147            ˇ
 5148        );
 5149    "});
 5150
 5151    // Paste it at the same position.
 5152    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5153    cx.assert_editor_state(indoc! {"
 5154        const a: B = (
 5155            c(),
 5156            d(
 5157                e,
 5158                f
 5159 5160        );
 5161    "});
 5162
 5163    // Paste it at a line with a lower indent level.
 5164    cx.set_state(indoc! {"
 5165        ˇ
 5166        const a: B = (
 5167            c(),
 5168        );
 5169    "});
 5170    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5171    cx.assert_editor_state(indoc! {"
 5172        d(
 5173            e,
 5174            f
 5175 5176        const a: B = (
 5177            c(),
 5178        );
 5179    "});
 5180
 5181    // Cut an indented block, with the leading whitespace.
 5182    cx.set_state(indoc! {"
 5183        const a: B = (
 5184            c(),
 5185        «    d(
 5186                e,
 5187                f
 5188            )
 5189        ˇ»);
 5190    "});
 5191    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5192    cx.assert_editor_state(indoc! {"
 5193        const a: B = (
 5194            c(),
 5195        ˇ);
 5196    "});
 5197
 5198    // Paste it at the same position.
 5199    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5200    cx.assert_editor_state(indoc! {"
 5201        const a: B = (
 5202            c(),
 5203            d(
 5204                e,
 5205                f
 5206            )
 5207        ˇ);
 5208    "});
 5209
 5210    // Paste it at a line with a higher indent level.
 5211    cx.set_state(indoc! {"
 5212        const a: B = (
 5213            c(),
 5214            d(
 5215                e,
 5216 5217            )
 5218        );
 5219    "});
 5220    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5221    cx.assert_editor_state(indoc! {"
 5222        const a: B = (
 5223            c(),
 5224            d(
 5225                e,
 5226                f    d(
 5227                    e,
 5228                    f
 5229                )
 5230        ˇ
 5231            )
 5232        );
 5233    "});
 5234
 5235    // Copy an indented block, starting mid-line
 5236    cx.set_state(indoc! {"
 5237        const a: B = (
 5238            c(),
 5239            somethin«g(
 5240                e,
 5241                f
 5242            )ˇ»
 5243        );
 5244    "});
 5245    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5246
 5247    // Paste it on a line with a lower indent level
 5248    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5249    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5250    cx.assert_editor_state(indoc! {"
 5251        const a: B = (
 5252            c(),
 5253            something(
 5254                e,
 5255                f
 5256            )
 5257        );
 5258        g(
 5259            e,
 5260            f
 5261"});
 5262}
 5263
 5264#[gpui::test]
 5265async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5266    init_test(cx, |_| {});
 5267
 5268    cx.write_to_clipboard(ClipboardItem::new_string(
 5269        "    d(\n        e\n    );\n".into(),
 5270    ));
 5271
 5272    let mut cx = EditorTestContext::new(cx).await;
 5273    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5274
 5275    cx.set_state(indoc! {"
 5276        fn a() {
 5277            b();
 5278            if c() {
 5279                ˇ
 5280            }
 5281        }
 5282    "});
 5283
 5284    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5285    cx.assert_editor_state(indoc! {"
 5286        fn a() {
 5287            b();
 5288            if c() {
 5289                d(
 5290                    e
 5291                );
 5292        ˇ
 5293            }
 5294        }
 5295    "});
 5296
 5297    cx.set_state(indoc! {"
 5298        fn a() {
 5299            b();
 5300            ˇ
 5301        }
 5302    "});
 5303
 5304    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5305    cx.assert_editor_state(indoc! {"
 5306        fn a() {
 5307            b();
 5308            d(
 5309                e
 5310            );
 5311        ˇ
 5312        }
 5313    "});
 5314}
 5315
 5316#[gpui::test]
 5317fn test_select_all(cx: &mut TestAppContext) {
 5318    init_test(cx, |_| {});
 5319
 5320    let editor = cx.add_window(|window, cx| {
 5321        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5322        build_editor(buffer, window, cx)
 5323    });
 5324    _ = editor.update(cx, |editor, window, cx| {
 5325        editor.select_all(&SelectAll, window, cx);
 5326        assert_eq!(
 5327            editor.selections.display_ranges(cx),
 5328            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5329        );
 5330    });
 5331}
 5332
 5333#[gpui::test]
 5334fn test_select_line(cx: &mut TestAppContext) {
 5335    init_test(cx, |_| {});
 5336
 5337    let editor = cx.add_window(|window, cx| {
 5338        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5339        build_editor(buffer, window, cx)
 5340    });
 5341    _ = editor.update(cx, |editor, window, cx| {
 5342        editor.change_selections(None, window, cx, |s| {
 5343            s.select_display_ranges([
 5344                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5345                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5346                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5347                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5348            ])
 5349        });
 5350        editor.select_line(&SelectLine, window, cx);
 5351        assert_eq!(
 5352            editor.selections.display_ranges(cx),
 5353            vec![
 5354                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5355                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5356            ]
 5357        );
 5358    });
 5359
 5360    _ = editor.update(cx, |editor, window, cx| {
 5361        editor.select_line(&SelectLine, window, cx);
 5362        assert_eq!(
 5363            editor.selections.display_ranges(cx),
 5364            vec![
 5365                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5366                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5367            ]
 5368        );
 5369    });
 5370
 5371    _ = editor.update(cx, |editor, window, cx| {
 5372        editor.select_line(&SelectLine, window, cx);
 5373        assert_eq!(
 5374            editor.selections.display_ranges(cx),
 5375            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5376        );
 5377    });
 5378}
 5379
 5380#[gpui::test]
 5381async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5382    init_test(cx, |_| {});
 5383    let mut cx = EditorTestContext::new(cx).await;
 5384
 5385    #[track_caller]
 5386    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5387        cx.set_state(initial_state);
 5388        cx.update_editor(|e, window, cx| {
 5389            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5390        });
 5391        cx.assert_editor_state(expected_state);
 5392    }
 5393
 5394    // Selection starts and ends at the middle of lines, left-to-right
 5395    test(
 5396        &mut cx,
 5397        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5398        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5399    );
 5400    // Same thing, right-to-left
 5401    test(
 5402        &mut cx,
 5403        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5404        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5405    );
 5406
 5407    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5408    test(
 5409        &mut cx,
 5410        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5411        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5412    );
 5413    // Same thing, right-to-left
 5414    test(
 5415        &mut cx,
 5416        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5417        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5418    );
 5419
 5420    // Whole buffer, left-to-right, last line ends with newline
 5421    test(
 5422        &mut cx,
 5423        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5424        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5425    );
 5426    // Same thing, right-to-left
 5427    test(
 5428        &mut cx,
 5429        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5430        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5431    );
 5432
 5433    // Starts at the end of a line, ends at the start of another
 5434    test(
 5435        &mut cx,
 5436        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5437        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5438    );
 5439}
 5440
 5441#[gpui::test]
 5442async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5443    init_test(cx, |_| {});
 5444
 5445    let editor = cx.add_window(|window, cx| {
 5446        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5447        build_editor(buffer, window, cx)
 5448    });
 5449
 5450    // setup
 5451    _ = editor.update(cx, |editor, window, cx| {
 5452        editor.fold_creases(
 5453            vec![
 5454                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5455                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5456                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5457            ],
 5458            true,
 5459            window,
 5460            cx,
 5461        );
 5462        assert_eq!(
 5463            editor.display_text(cx),
 5464            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5465        );
 5466    });
 5467
 5468    _ = editor.update(cx, |editor, window, cx| {
 5469        editor.change_selections(None, window, cx, |s| {
 5470            s.select_display_ranges([
 5471                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5472                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5473                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5474                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5475            ])
 5476        });
 5477        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5478        assert_eq!(
 5479            editor.display_text(cx),
 5480            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5481        );
 5482    });
 5483    EditorTestContext::for_editor(editor, cx)
 5484        .await
 5485        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5486
 5487    _ = editor.update(cx, |editor, window, cx| {
 5488        editor.change_selections(None, window, cx, |s| {
 5489            s.select_display_ranges([
 5490                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5491            ])
 5492        });
 5493        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5494        assert_eq!(
 5495            editor.display_text(cx),
 5496            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5497        );
 5498        assert_eq!(
 5499            editor.selections.display_ranges(cx),
 5500            [
 5501                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5502                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5503                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5504                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5505                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5506                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5507                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5508            ]
 5509        );
 5510    });
 5511    EditorTestContext::for_editor(editor, cx)
 5512        .await
 5513        .assert_editor_state(
 5514            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5515        );
 5516}
 5517
 5518#[gpui::test]
 5519async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5520    init_test(cx, |_| {});
 5521
 5522    let mut cx = EditorTestContext::new(cx).await;
 5523
 5524    cx.set_state(indoc!(
 5525        r#"abc
 5526           defˇghi
 5527
 5528           jk
 5529           nlmo
 5530           "#
 5531    ));
 5532
 5533    cx.update_editor(|editor, window, cx| {
 5534        editor.add_selection_above(&Default::default(), window, cx);
 5535    });
 5536
 5537    cx.assert_editor_state(indoc!(
 5538        r#"abcˇ
 5539           defˇghi
 5540
 5541           jk
 5542           nlmo
 5543           "#
 5544    ));
 5545
 5546    cx.update_editor(|editor, window, cx| {
 5547        editor.add_selection_above(&Default::default(), window, cx);
 5548    });
 5549
 5550    cx.assert_editor_state(indoc!(
 5551        r#"abcˇ
 5552            defˇghi
 5553
 5554            jk
 5555            nlmo
 5556            "#
 5557    ));
 5558
 5559    cx.update_editor(|editor, window, cx| {
 5560        editor.add_selection_below(&Default::default(), window, cx);
 5561    });
 5562
 5563    cx.assert_editor_state(indoc!(
 5564        r#"abc
 5565           defˇghi
 5566
 5567           jk
 5568           nlmo
 5569           "#
 5570    ));
 5571
 5572    cx.update_editor(|editor, window, cx| {
 5573        editor.undo_selection(&Default::default(), window, cx);
 5574    });
 5575
 5576    cx.assert_editor_state(indoc!(
 5577        r#"abcˇ
 5578           defˇghi
 5579
 5580           jk
 5581           nlmo
 5582           "#
 5583    ));
 5584
 5585    cx.update_editor(|editor, window, cx| {
 5586        editor.redo_selection(&Default::default(), window, cx);
 5587    });
 5588
 5589    cx.assert_editor_state(indoc!(
 5590        r#"abc
 5591           defˇghi
 5592
 5593           jk
 5594           nlmo
 5595           "#
 5596    ));
 5597
 5598    cx.update_editor(|editor, window, cx| {
 5599        editor.add_selection_below(&Default::default(), window, cx);
 5600    });
 5601
 5602    cx.assert_editor_state(indoc!(
 5603        r#"abc
 5604           defˇghi
 5605
 5606           jk
 5607           nlmˇo
 5608           "#
 5609    ));
 5610
 5611    cx.update_editor(|editor, window, cx| {
 5612        editor.add_selection_below(&Default::default(), window, cx);
 5613    });
 5614
 5615    cx.assert_editor_state(indoc!(
 5616        r#"abc
 5617           defˇghi
 5618
 5619           jk
 5620           nlmˇo
 5621           "#
 5622    ));
 5623
 5624    // change selections
 5625    cx.set_state(indoc!(
 5626        r#"abc
 5627           def«ˇg»hi
 5628
 5629           jk
 5630           nlmo
 5631           "#
 5632    ));
 5633
 5634    cx.update_editor(|editor, window, cx| {
 5635        editor.add_selection_below(&Default::default(), window, cx);
 5636    });
 5637
 5638    cx.assert_editor_state(indoc!(
 5639        r#"abc
 5640           def«ˇg»hi
 5641
 5642           jk
 5643           nlm«ˇo»
 5644           "#
 5645    ));
 5646
 5647    cx.update_editor(|editor, window, cx| {
 5648        editor.add_selection_below(&Default::default(), window, cx);
 5649    });
 5650
 5651    cx.assert_editor_state(indoc!(
 5652        r#"abc
 5653           def«ˇg»hi
 5654
 5655           jk
 5656           nlm«ˇo»
 5657           "#
 5658    ));
 5659
 5660    cx.update_editor(|editor, window, cx| {
 5661        editor.add_selection_above(&Default::default(), window, cx);
 5662    });
 5663
 5664    cx.assert_editor_state(indoc!(
 5665        r#"abc
 5666           def«ˇg»hi
 5667
 5668           jk
 5669           nlmo
 5670           "#
 5671    ));
 5672
 5673    cx.update_editor(|editor, window, cx| {
 5674        editor.add_selection_above(&Default::default(), window, cx);
 5675    });
 5676
 5677    cx.assert_editor_state(indoc!(
 5678        r#"abc
 5679           def«ˇg»hi
 5680
 5681           jk
 5682           nlmo
 5683           "#
 5684    ));
 5685
 5686    // Change selections again
 5687    cx.set_state(indoc!(
 5688        r#"a«bc
 5689           defgˇ»hi
 5690
 5691           jk
 5692           nlmo
 5693           "#
 5694    ));
 5695
 5696    cx.update_editor(|editor, window, cx| {
 5697        editor.add_selection_below(&Default::default(), window, cx);
 5698    });
 5699
 5700    cx.assert_editor_state(indoc!(
 5701        r#"a«bcˇ»
 5702           d«efgˇ»hi
 5703
 5704           j«kˇ»
 5705           nlmo
 5706           "#
 5707    ));
 5708
 5709    cx.update_editor(|editor, window, cx| {
 5710        editor.add_selection_below(&Default::default(), window, cx);
 5711    });
 5712    cx.assert_editor_state(indoc!(
 5713        r#"a«bcˇ»
 5714           d«efgˇ»hi
 5715
 5716           j«kˇ»
 5717           n«lmoˇ»
 5718           "#
 5719    ));
 5720    cx.update_editor(|editor, window, cx| {
 5721        editor.add_selection_above(&Default::default(), window, cx);
 5722    });
 5723
 5724    cx.assert_editor_state(indoc!(
 5725        r#"a«bcˇ»
 5726           d«efgˇ»hi
 5727
 5728           j«kˇ»
 5729           nlmo
 5730           "#
 5731    ));
 5732
 5733    // Change selections again
 5734    cx.set_state(indoc!(
 5735        r#"abc
 5736           d«ˇefghi
 5737
 5738           jk
 5739           nlm»o
 5740           "#
 5741    ));
 5742
 5743    cx.update_editor(|editor, window, cx| {
 5744        editor.add_selection_above(&Default::default(), window, cx);
 5745    });
 5746
 5747    cx.assert_editor_state(indoc!(
 5748        r#"a«ˇbc»
 5749           d«ˇef»ghi
 5750
 5751           j«ˇk»
 5752           n«ˇlm»o
 5753           "#
 5754    ));
 5755
 5756    cx.update_editor(|editor, window, cx| {
 5757        editor.add_selection_below(&Default::default(), window, cx);
 5758    });
 5759
 5760    cx.assert_editor_state(indoc!(
 5761        r#"abc
 5762           d«ˇef»ghi
 5763
 5764           j«ˇk»
 5765           n«ˇlm»o
 5766           "#
 5767    ));
 5768}
 5769
 5770#[gpui::test]
 5771async fn test_select_next(cx: &mut TestAppContext) {
 5772    init_test(cx, |_| {});
 5773
 5774    let mut cx = EditorTestContext::new(cx).await;
 5775    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5776
 5777    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5778        .unwrap();
 5779    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5780
 5781    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5782        .unwrap();
 5783    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5784
 5785    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5786    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5787
 5788    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5789    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5790
 5791    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5792        .unwrap();
 5793    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5794
 5795    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5796        .unwrap();
 5797    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5798}
 5799
 5800#[gpui::test]
 5801async fn test_select_all_matches(cx: &mut TestAppContext) {
 5802    init_test(cx, |_| {});
 5803
 5804    let mut cx = EditorTestContext::new(cx).await;
 5805
 5806    // Test caret-only selections
 5807    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5808    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5809        .unwrap();
 5810    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5811
 5812    // Test left-to-right selections
 5813    cx.set_state("abc\n«abcˇ»\nabc");
 5814    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5815        .unwrap();
 5816    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 5817
 5818    // Test right-to-left selections
 5819    cx.set_state("abc\n«ˇabc»\nabc");
 5820    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5821        .unwrap();
 5822    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 5823
 5824    // Test selecting whitespace with caret selection
 5825    cx.set_state("abc\nˇ   abc\nabc");
 5826    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5827        .unwrap();
 5828    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 5829
 5830    // Test selecting whitespace with left-to-right selection
 5831    cx.set_state("abc\n«ˇ  »abc\nabc");
 5832    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5833        .unwrap();
 5834    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 5835
 5836    // Test no matches with right-to-left selection
 5837    cx.set_state("abc\n«  ˇ»abc\nabc");
 5838    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5839        .unwrap();
 5840    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 5841}
 5842
 5843#[gpui::test]
 5844async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 5845    init_test(cx, |_| {});
 5846
 5847    let mut cx = EditorTestContext::new(cx).await;
 5848
 5849    let large_body_1 = "\nd".repeat(200);
 5850    let large_body_2 = "\ne".repeat(200);
 5851
 5852    cx.set_state(&format!(
 5853        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 5854    ));
 5855    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 5856        let scroll_position = editor.scroll_position(cx);
 5857        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 5858        scroll_position
 5859    });
 5860
 5861    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5862        .unwrap();
 5863    cx.assert_editor_state(&format!(
 5864        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 5865    ));
 5866    let scroll_position_after_selection =
 5867        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 5868    assert_eq!(
 5869        initial_scroll_position, scroll_position_after_selection,
 5870        "Scroll position should not change after selecting all matches"
 5871    );
 5872}
 5873
 5874#[gpui::test]
 5875async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 5876    init_test(cx, |_| {});
 5877
 5878    let mut cx = EditorLspTestContext::new_rust(
 5879        lsp::ServerCapabilities {
 5880            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5881            ..Default::default()
 5882        },
 5883        cx,
 5884    )
 5885    .await;
 5886
 5887    cx.set_state(indoc! {"
 5888        line 1
 5889        line 2
 5890        linˇe 3
 5891        line 4
 5892        line 5
 5893    "});
 5894
 5895    // Make an edit
 5896    cx.update_editor(|editor, window, cx| {
 5897        editor.handle_input("X", window, cx);
 5898    });
 5899
 5900    // Move cursor to a different position
 5901    cx.update_editor(|editor, window, cx| {
 5902        editor.change_selections(None, window, cx, |s| {
 5903            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 5904        });
 5905    });
 5906
 5907    cx.assert_editor_state(indoc! {"
 5908        line 1
 5909        line 2
 5910        linXe 3
 5911        line 4
 5912        liˇne 5
 5913    "});
 5914
 5915    cx.lsp
 5916        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 5917            Ok(Some(vec![lsp::TextEdit::new(
 5918                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 5919                "PREFIX ".to_string(),
 5920            )]))
 5921        });
 5922
 5923    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 5924        .unwrap()
 5925        .await
 5926        .unwrap();
 5927
 5928    cx.assert_editor_state(indoc! {"
 5929        PREFIX line 1
 5930        line 2
 5931        linXe 3
 5932        line 4
 5933        liˇne 5
 5934    "});
 5935
 5936    // Undo formatting
 5937    cx.update_editor(|editor, window, cx| {
 5938        editor.undo(&Default::default(), window, cx);
 5939    });
 5940
 5941    // Verify cursor moved back to position after edit
 5942    cx.assert_editor_state(indoc! {"
 5943        line 1
 5944        line 2
 5945        linXˇe 3
 5946        line 4
 5947        line 5
 5948    "});
 5949}
 5950
 5951#[gpui::test]
 5952async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 5953    init_test(cx, |_| {});
 5954
 5955    let mut cx = EditorTestContext::new(cx).await;
 5956    cx.set_state(
 5957        r#"let foo = 2;
 5958lˇet foo = 2;
 5959let fooˇ = 2;
 5960let foo = 2;
 5961let foo = ˇ2;"#,
 5962    );
 5963
 5964    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5965        .unwrap();
 5966    cx.assert_editor_state(
 5967        r#"let foo = 2;
 5968«letˇ» foo = 2;
 5969let «fooˇ» = 2;
 5970let foo = 2;
 5971let foo = «2ˇ»;"#,
 5972    );
 5973
 5974    // noop for multiple selections with different contents
 5975    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5976        .unwrap();
 5977    cx.assert_editor_state(
 5978        r#"let foo = 2;
 5979«letˇ» foo = 2;
 5980let «fooˇ» = 2;
 5981let foo = 2;
 5982let foo = «2ˇ»;"#,
 5983    );
 5984}
 5985
 5986#[gpui::test]
 5987async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 5988    init_test(cx, |_| {});
 5989
 5990    let mut cx =
 5991        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 5992
 5993    cx.assert_editor_state(indoc! {"
 5994        ˇbbb
 5995        ccc
 5996
 5997        bbb
 5998        ccc
 5999        "});
 6000    cx.dispatch_action(SelectPrevious::default());
 6001    cx.assert_editor_state(indoc! {"
 6002                «bbbˇ»
 6003                ccc
 6004
 6005                bbb
 6006                ccc
 6007                "});
 6008    cx.dispatch_action(SelectPrevious::default());
 6009    cx.assert_editor_state(indoc! {"
 6010                «bbbˇ»
 6011                ccc
 6012
 6013                «bbbˇ»
 6014                ccc
 6015                "});
 6016}
 6017
 6018#[gpui::test]
 6019async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6020    init_test(cx, |_| {});
 6021
 6022    let mut cx = EditorTestContext::new(cx).await;
 6023    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6024
 6025    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6026        .unwrap();
 6027    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6028
 6029    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6030        .unwrap();
 6031    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6032
 6033    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6034    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6035
 6036    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6037    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6038
 6039    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6040        .unwrap();
 6041    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6042
 6043    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6044        .unwrap();
 6045    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 6046
 6047    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6048        .unwrap();
 6049    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 6050}
 6051
 6052#[gpui::test]
 6053async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6054    init_test(cx, |_| {});
 6055
 6056    let mut cx = EditorTestContext::new(cx).await;
 6057    cx.set_state("");
 6058
 6059    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6060        .unwrap();
 6061    cx.assert_editor_state("«aˇ»");
 6062    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6063        .unwrap();
 6064    cx.assert_editor_state("«aˇ»");
 6065}
 6066
 6067#[gpui::test]
 6068async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6069    init_test(cx, |_| {});
 6070
 6071    let mut cx = EditorTestContext::new(cx).await;
 6072    cx.set_state(
 6073        r#"let foo = 2;
 6074lˇet foo = 2;
 6075let fooˇ = 2;
 6076let foo = 2;
 6077let foo = ˇ2;"#,
 6078    );
 6079
 6080    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6081        .unwrap();
 6082    cx.assert_editor_state(
 6083        r#"let foo = 2;
 6084«letˇ» foo = 2;
 6085let «fooˇ» = 2;
 6086let foo = 2;
 6087let foo = «2ˇ»;"#,
 6088    );
 6089
 6090    // noop for multiple selections with different contents
 6091    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6092        .unwrap();
 6093    cx.assert_editor_state(
 6094        r#"let foo = 2;
 6095«letˇ» foo = 2;
 6096let «fooˇ» = 2;
 6097let foo = 2;
 6098let foo = «2ˇ»;"#,
 6099    );
 6100}
 6101
 6102#[gpui::test]
 6103async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6104    init_test(cx, |_| {});
 6105
 6106    let mut cx = EditorTestContext::new(cx).await;
 6107    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6108
 6109    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6110        .unwrap();
 6111    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6112
 6113    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6114        .unwrap();
 6115    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6116
 6117    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6118    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6119
 6120    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6121    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6122
 6123    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6124        .unwrap();
 6125    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 6126
 6127    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6128        .unwrap();
 6129    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 6130}
 6131
 6132#[gpui::test]
 6133async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6134    init_test(cx, |_| {});
 6135
 6136    let language = Arc::new(Language::new(
 6137        LanguageConfig::default(),
 6138        Some(tree_sitter_rust::LANGUAGE.into()),
 6139    ));
 6140
 6141    let text = r#"
 6142        use mod1::mod2::{mod3, mod4};
 6143
 6144        fn fn_1(param1: bool, param2: &str) {
 6145            let var1 = "text";
 6146        }
 6147    "#
 6148    .unindent();
 6149
 6150    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6151    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6152    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6153
 6154    editor
 6155        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6156        .await;
 6157
 6158    editor.update_in(cx, |editor, window, cx| {
 6159        editor.change_selections(None, window, cx, |s| {
 6160            s.select_display_ranges([
 6161                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6162                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6163                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6164            ]);
 6165        });
 6166        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6167    });
 6168    editor.update(cx, |editor, cx| {
 6169        assert_text_with_selections(
 6170            editor,
 6171            indoc! {r#"
 6172                use mod1::mod2::{mod3, «mod4ˇ»};
 6173
 6174                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6175                    let var1 = "«ˇtext»";
 6176                }
 6177            "#},
 6178            cx,
 6179        );
 6180    });
 6181
 6182    editor.update_in(cx, |editor, window, cx| {
 6183        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6184    });
 6185    editor.update(cx, |editor, cx| {
 6186        assert_text_with_selections(
 6187            editor,
 6188            indoc! {r#"
 6189                use mod1::mod2::«{mod3, mod4}ˇ»;
 6190
 6191                «ˇfn fn_1(param1: bool, param2: &str) {
 6192                    let var1 = "text";
 6193 6194            "#},
 6195            cx,
 6196        );
 6197    });
 6198
 6199    editor.update_in(cx, |editor, window, cx| {
 6200        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6201    });
 6202    assert_eq!(
 6203        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6204        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6205    );
 6206
 6207    // Trying to expand the selected syntax node one more time has no effect.
 6208    editor.update_in(cx, |editor, window, cx| {
 6209        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6210    });
 6211    assert_eq!(
 6212        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6213        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6214    );
 6215
 6216    editor.update_in(cx, |editor, window, cx| {
 6217        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6218    });
 6219    editor.update(cx, |editor, cx| {
 6220        assert_text_with_selections(
 6221            editor,
 6222            indoc! {r#"
 6223                use mod1::mod2::«{mod3, mod4}ˇ»;
 6224
 6225                «ˇfn fn_1(param1: bool, param2: &str) {
 6226                    let var1 = "text";
 6227 6228            "#},
 6229            cx,
 6230        );
 6231    });
 6232
 6233    editor.update_in(cx, |editor, window, cx| {
 6234        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6235    });
 6236    editor.update(cx, |editor, cx| {
 6237        assert_text_with_selections(
 6238            editor,
 6239            indoc! {r#"
 6240                use mod1::mod2::{mod3, «mod4ˇ»};
 6241
 6242                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6243                    let var1 = "«ˇtext»";
 6244                }
 6245            "#},
 6246            cx,
 6247        );
 6248    });
 6249
 6250    editor.update_in(cx, |editor, window, cx| {
 6251        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6252    });
 6253    editor.update(cx, |editor, cx| {
 6254        assert_text_with_selections(
 6255            editor,
 6256            indoc! {r#"
 6257                use mod1::mod2::{mod3, mo«ˇ»d4};
 6258
 6259                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6260                    let var1 = "te«ˇ»xt";
 6261                }
 6262            "#},
 6263            cx,
 6264        );
 6265    });
 6266
 6267    // Trying to shrink the selected syntax node one more time has no effect.
 6268    editor.update_in(cx, |editor, window, cx| {
 6269        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6270    });
 6271    editor.update_in(cx, |editor, _, cx| {
 6272        assert_text_with_selections(
 6273            editor,
 6274            indoc! {r#"
 6275                use mod1::mod2::{mod3, mo«ˇ»d4};
 6276
 6277                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6278                    let var1 = "te«ˇ»xt";
 6279                }
 6280            "#},
 6281            cx,
 6282        );
 6283    });
 6284
 6285    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6286    // a fold.
 6287    editor.update_in(cx, |editor, window, cx| {
 6288        editor.fold_creases(
 6289            vec![
 6290                Crease::simple(
 6291                    Point::new(0, 21)..Point::new(0, 24),
 6292                    FoldPlaceholder::test(),
 6293                ),
 6294                Crease::simple(
 6295                    Point::new(3, 20)..Point::new(3, 22),
 6296                    FoldPlaceholder::test(),
 6297                ),
 6298            ],
 6299            true,
 6300            window,
 6301            cx,
 6302        );
 6303        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6304    });
 6305    editor.update(cx, |editor, cx| {
 6306        assert_text_with_selections(
 6307            editor,
 6308            indoc! {r#"
 6309                use mod1::mod2::«{mod3, mod4}ˇ»;
 6310
 6311                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6312                    let var1 = "«ˇtext»";
 6313                }
 6314            "#},
 6315            cx,
 6316        );
 6317    });
 6318}
 6319
 6320#[gpui::test]
 6321async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6322    init_test(cx, |_| {});
 6323
 6324    let language = Arc::new(Language::new(
 6325        LanguageConfig::default(),
 6326        Some(tree_sitter_rust::LANGUAGE.into()),
 6327    ));
 6328
 6329    let text = r#"
 6330        use mod1::mod2::{mod3, mod4};
 6331
 6332        fn fn_1(param1: bool, param2: &str) {
 6333            let var1 = "hello world";
 6334        }
 6335    "#
 6336    .unindent();
 6337
 6338    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6339    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6340    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6341
 6342    editor
 6343        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6344        .await;
 6345
 6346    // Test 1: Cursor on a letter of a string word
 6347    editor.update_in(cx, |editor, window, cx| {
 6348        editor.change_selections(None, window, cx, |s| {
 6349            s.select_display_ranges([
 6350                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6351            ]);
 6352        });
 6353    });
 6354    editor.update_in(cx, |editor, window, cx| {
 6355        assert_text_with_selections(
 6356            editor,
 6357            indoc! {r#"
 6358                use mod1::mod2::{mod3, mod4};
 6359
 6360                fn fn_1(param1: bool, param2: &str) {
 6361                    let var1 = "hˇello world";
 6362                }
 6363            "#},
 6364            cx,
 6365        );
 6366        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6367        assert_text_with_selections(
 6368            editor,
 6369            indoc! {r#"
 6370                use mod1::mod2::{mod3, mod4};
 6371
 6372                fn fn_1(param1: bool, param2: &str) {
 6373                    let var1 = "«ˇhello» world";
 6374                }
 6375            "#},
 6376            cx,
 6377        );
 6378    });
 6379
 6380    // Test 2: Partial selection within a word
 6381    editor.update_in(cx, |editor, window, cx| {
 6382        editor.change_selections(None, window, cx, |s| {
 6383            s.select_display_ranges([
 6384                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 6385            ]);
 6386        });
 6387    });
 6388    editor.update_in(cx, |editor, window, cx| {
 6389        assert_text_with_selections(
 6390            editor,
 6391            indoc! {r#"
 6392                use mod1::mod2::{mod3, mod4};
 6393
 6394                fn fn_1(param1: bool, param2: &str) {
 6395                    let var1 = "h«elˇ»lo world";
 6396                }
 6397            "#},
 6398            cx,
 6399        );
 6400        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6401        assert_text_with_selections(
 6402            editor,
 6403            indoc! {r#"
 6404                use mod1::mod2::{mod3, mod4};
 6405
 6406                fn fn_1(param1: bool, param2: &str) {
 6407                    let var1 = "«ˇhello» world";
 6408                }
 6409            "#},
 6410            cx,
 6411        );
 6412    });
 6413
 6414    // Test 3: Complete word already selected
 6415    editor.update_in(cx, |editor, window, cx| {
 6416        editor.change_selections(None, window, cx, |s| {
 6417            s.select_display_ranges([
 6418                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 6419            ]);
 6420        });
 6421    });
 6422    editor.update_in(cx, |editor, window, cx| {
 6423        assert_text_with_selections(
 6424            editor,
 6425            indoc! {r#"
 6426                use mod1::mod2::{mod3, mod4};
 6427
 6428                fn fn_1(param1: bool, param2: &str) {
 6429                    let var1 = "«helloˇ» world";
 6430                }
 6431            "#},
 6432            cx,
 6433        );
 6434        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6435        assert_text_with_selections(
 6436            editor,
 6437            indoc! {r#"
 6438                use mod1::mod2::{mod3, mod4};
 6439
 6440                fn fn_1(param1: bool, param2: &str) {
 6441                    let var1 = "«hello worldˇ»";
 6442                }
 6443            "#},
 6444            cx,
 6445        );
 6446    });
 6447
 6448    // Test 4: Selection spanning across words
 6449    editor.update_in(cx, |editor, window, cx| {
 6450        editor.change_selections(None, window, cx, |s| {
 6451            s.select_display_ranges([
 6452                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 6453            ]);
 6454        });
 6455    });
 6456    editor.update_in(cx, |editor, window, cx| {
 6457        assert_text_with_selections(
 6458            editor,
 6459            indoc! {r#"
 6460                use mod1::mod2::{mod3, mod4};
 6461
 6462                fn fn_1(param1: bool, param2: &str) {
 6463                    let var1 = "hel«lo woˇ»rld";
 6464                }
 6465            "#},
 6466            cx,
 6467        );
 6468        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6469        assert_text_with_selections(
 6470            editor,
 6471            indoc! {r#"
 6472                use mod1::mod2::{mod3, mod4};
 6473
 6474                fn fn_1(param1: bool, param2: &str) {
 6475                    let var1 = "«ˇhello world»";
 6476                }
 6477            "#},
 6478            cx,
 6479        );
 6480    });
 6481
 6482    // Test 5: Expansion beyond string
 6483    editor.update_in(cx, |editor, window, cx| {
 6484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6486        assert_text_with_selections(
 6487            editor,
 6488            indoc! {r#"
 6489                use mod1::mod2::{mod3, mod4};
 6490
 6491                fn fn_1(param1: bool, param2: &str) {
 6492                    «ˇlet var1 = "hello world";»
 6493                }
 6494            "#},
 6495            cx,
 6496        );
 6497    });
 6498}
 6499
 6500#[gpui::test]
 6501async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6502    init_test(cx, |_| {});
 6503
 6504    let base_text = r#"
 6505        impl A {
 6506            // this is an uncommitted comment
 6507
 6508            fn b() {
 6509                c();
 6510            }
 6511
 6512            // this is another uncommitted comment
 6513
 6514            fn d() {
 6515                // e
 6516                // f
 6517            }
 6518        }
 6519
 6520        fn g() {
 6521            // h
 6522        }
 6523    "#
 6524    .unindent();
 6525
 6526    let text = r#"
 6527        ˇimpl A {
 6528
 6529            fn b() {
 6530                c();
 6531            }
 6532
 6533            fn d() {
 6534                // e
 6535                // f
 6536            }
 6537        }
 6538
 6539        fn g() {
 6540            // h
 6541        }
 6542    "#
 6543    .unindent();
 6544
 6545    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6546    cx.set_state(&text);
 6547    cx.set_head_text(&base_text);
 6548    cx.update_editor(|editor, window, cx| {
 6549        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6550    });
 6551
 6552    cx.assert_state_with_diff(
 6553        "
 6554        ˇimpl A {
 6555      -     // this is an uncommitted comment
 6556
 6557            fn b() {
 6558                c();
 6559            }
 6560
 6561      -     // this is another uncommitted comment
 6562      -
 6563            fn d() {
 6564                // e
 6565                // f
 6566            }
 6567        }
 6568
 6569        fn g() {
 6570            // h
 6571        }
 6572    "
 6573        .unindent(),
 6574    );
 6575
 6576    let expected_display_text = "
 6577        impl A {
 6578            // this is an uncommitted comment
 6579
 6580            fn b() {
 6581 6582            }
 6583
 6584            // this is another uncommitted comment
 6585
 6586            fn d() {
 6587 6588            }
 6589        }
 6590
 6591        fn g() {
 6592 6593        }
 6594        "
 6595    .unindent();
 6596
 6597    cx.update_editor(|editor, window, cx| {
 6598        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 6599        assert_eq!(editor.display_text(cx), expected_display_text);
 6600    });
 6601}
 6602
 6603#[gpui::test]
 6604async fn test_autoindent(cx: &mut TestAppContext) {
 6605    init_test(cx, |_| {});
 6606
 6607    let language = Arc::new(
 6608        Language::new(
 6609            LanguageConfig {
 6610                brackets: BracketPairConfig {
 6611                    pairs: vec![
 6612                        BracketPair {
 6613                            start: "{".to_string(),
 6614                            end: "}".to_string(),
 6615                            close: false,
 6616                            surround: false,
 6617                            newline: true,
 6618                        },
 6619                        BracketPair {
 6620                            start: "(".to_string(),
 6621                            end: ")".to_string(),
 6622                            close: false,
 6623                            surround: false,
 6624                            newline: true,
 6625                        },
 6626                    ],
 6627                    ..Default::default()
 6628                },
 6629                ..Default::default()
 6630            },
 6631            Some(tree_sitter_rust::LANGUAGE.into()),
 6632        )
 6633        .with_indents_query(
 6634            r#"
 6635                (_ "(" ")" @end) @indent
 6636                (_ "{" "}" @end) @indent
 6637            "#,
 6638        )
 6639        .unwrap(),
 6640    );
 6641
 6642    let text = "fn a() {}";
 6643
 6644    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6645    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6646    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6647    editor
 6648        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6649        .await;
 6650
 6651    editor.update_in(cx, |editor, window, cx| {
 6652        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 6653        editor.newline(&Newline, window, cx);
 6654        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 6655        assert_eq!(
 6656            editor.selections.ranges(cx),
 6657            &[
 6658                Point::new(1, 4)..Point::new(1, 4),
 6659                Point::new(3, 4)..Point::new(3, 4),
 6660                Point::new(5, 0)..Point::new(5, 0)
 6661            ]
 6662        );
 6663    });
 6664}
 6665
 6666#[gpui::test]
 6667async fn test_autoindent_selections(cx: &mut TestAppContext) {
 6668    init_test(cx, |_| {});
 6669
 6670    {
 6671        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6672        cx.set_state(indoc! {"
 6673            impl A {
 6674
 6675                fn b() {}
 6676
 6677            «fn c() {
 6678
 6679            }ˇ»
 6680            }
 6681        "});
 6682
 6683        cx.update_editor(|editor, window, cx| {
 6684            editor.autoindent(&Default::default(), window, cx);
 6685        });
 6686
 6687        cx.assert_editor_state(indoc! {"
 6688            impl A {
 6689
 6690                fn b() {}
 6691
 6692                «fn c() {
 6693
 6694                }ˇ»
 6695            }
 6696        "});
 6697    }
 6698
 6699    {
 6700        let mut cx = EditorTestContext::new_multibuffer(
 6701            cx,
 6702            [indoc! { "
 6703                impl A {
 6704                «
 6705                // a
 6706                fn b(){}
 6707                »
 6708                «
 6709                    }
 6710                    fn c(){}
 6711                »
 6712            "}],
 6713        );
 6714
 6715        let buffer = cx.update_editor(|editor, _, cx| {
 6716            let buffer = editor.buffer().update(cx, |buffer, _| {
 6717                buffer.all_buffers().iter().next().unwrap().clone()
 6718            });
 6719            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6720            buffer
 6721        });
 6722
 6723        cx.run_until_parked();
 6724        cx.update_editor(|editor, window, cx| {
 6725            editor.select_all(&Default::default(), window, cx);
 6726            editor.autoindent(&Default::default(), window, cx)
 6727        });
 6728        cx.run_until_parked();
 6729
 6730        cx.update(|_, cx| {
 6731            assert_eq!(
 6732                buffer.read(cx).text(),
 6733                indoc! { "
 6734                    impl A {
 6735
 6736                        // a
 6737                        fn b(){}
 6738
 6739
 6740                    }
 6741                    fn c(){}
 6742
 6743                " }
 6744            )
 6745        });
 6746    }
 6747}
 6748
 6749#[gpui::test]
 6750async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 6751    init_test(cx, |_| {});
 6752
 6753    let mut cx = EditorTestContext::new(cx).await;
 6754
 6755    let language = Arc::new(Language::new(
 6756        LanguageConfig {
 6757            brackets: BracketPairConfig {
 6758                pairs: vec![
 6759                    BracketPair {
 6760                        start: "{".to_string(),
 6761                        end: "}".to_string(),
 6762                        close: true,
 6763                        surround: true,
 6764                        newline: true,
 6765                    },
 6766                    BracketPair {
 6767                        start: "(".to_string(),
 6768                        end: ")".to_string(),
 6769                        close: true,
 6770                        surround: true,
 6771                        newline: true,
 6772                    },
 6773                    BracketPair {
 6774                        start: "/*".to_string(),
 6775                        end: " */".to_string(),
 6776                        close: true,
 6777                        surround: true,
 6778                        newline: true,
 6779                    },
 6780                    BracketPair {
 6781                        start: "[".to_string(),
 6782                        end: "]".to_string(),
 6783                        close: false,
 6784                        surround: false,
 6785                        newline: true,
 6786                    },
 6787                    BracketPair {
 6788                        start: "\"".to_string(),
 6789                        end: "\"".to_string(),
 6790                        close: true,
 6791                        surround: true,
 6792                        newline: false,
 6793                    },
 6794                    BracketPair {
 6795                        start: "<".to_string(),
 6796                        end: ">".to_string(),
 6797                        close: false,
 6798                        surround: true,
 6799                        newline: true,
 6800                    },
 6801                ],
 6802                ..Default::default()
 6803            },
 6804            autoclose_before: "})]".to_string(),
 6805            ..Default::default()
 6806        },
 6807        Some(tree_sitter_rust::LANGUAGE.into()),
 6808    ));
 6809
 6810    cx.language_registry().add(language.clone());
 6811    cx.update_buffer(|buffer, cx| {
 6812        buffer.set_language(Some(language), cx);
 6813    });
 6814
 6815    cx.set_state(
 6816        &r#"
 6817            🏀ˇ
 6818            εˇ
 6819            ❤️ˇ
 6820        "#
 6821        .unindent(),
 6822    );
 6823
 6824    // autoclose multiple nested brackets at multiple cursors
 6825    cx.update_editor(|editor, window, cx| {
 6826        editor.handle_input("{", window, cx);
 6827        editor.handle_input("{", window, cx);
 6828        editor.handle_input("{", window, cx);
 6829    });
 6830    cx.assert_editor_state(
 6831        &"
 6832            🏀{{{ˇ}}}
 6833            ε{{{ˇ}}}
 6834            ❤️{{{ˇ}}}
 6835        "
 6836        .unindent(),
 6837    );
 6838
 6839    // insert a different closing bracket
 6840    cx.update_editor(|editor, window, cx| {
 6841        editor.handle_input(")", window, cx);
 6842    });
 6843    cx.assert_editor_state(
 6844        &"
 6845            🏀{{{)ˇ}}}
 6846            ε{{{)ˇ}}}
 6847            ❤️{{{)ˇ}}}
 6848        "
 6849        .unindent(),
 6850    );
 6851
 6852    // skip over the auto-closed brackets when typing a closing bracket
 6853    cx.update_editor(|editor, window, cx| {
 6854        editor.move_right(&MoveRight, window, cx);
 6855        editor.handle_input("}", window, cx);
 6856        editor.handle_input("}", window, cx);
 6857        editor.handle_input("}", window, cx);
 6858    });
 6859    cx.assert_editor_state(
 6860        &"
 6861            🏀{{{)}}}}ˇ
 6862            ε{{{)}}}}ˇ
 6863            ❤️{{{)}}}}ˇ
 6864        "
 6865        .unindent(),
 6866    );
 6867
 6868    // autoclose multi-character pairs
 6869    cx.set_state(
 6870        &"
 6871            ˇ
 6872            ˇ
 6873        "
 6874        .unindent(),
 6875    );
 6876    cx.update_editor(|editor, window, cx| {
 6877        editor.handle_input("/", window, cx);
 6878        editor.handle_input("*", window, cx);
 6879    });
 6880    cx.assert_editor_state(
 6881        &"
 6882            /*ˇ */
 6883            /*ˇ */
 6884        "
 6885        .unindent(),
 6886    );
 6887
 6888    // one cursor autocloses a multi-character pair, one cursor
 6889    // does not autoclose.
 6890    cx.set_state(
 6891        &"
 6892 6893            ˇ
 6894        "
 6895        .unindent(),
 6896    );
 6897    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 6898    cx.assert_editor_state(
 6899        &"
 6900            /*ˇ */
 6901 6902        "
 6903        .unindent(),
 6904    );
 6905
 6906    // Don't autoclose if the next character isn't whitespace and isn't
 6907    // listed in the language's "autoclose_before" section.
 6908    cx.set_state("ˇa b");
 6909    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6910    cx.assert_editor_state("{ˇa b");
 6911
 6912    // Don't autoclose if `close` is false for the bracket pair
 6913    cx.set_state("ˇ");
 6914    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 6915    cx.assert_editor_state("");
 6916
 6917    // Surround with brackets if text is selected
 6918    cx.set_state("«aˇ» b");
 6919    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6920    cx.assert_editor_state("{«aˇ»} b");
 6921
 6922    // Autoclose when not immediately after a word character
 6923    cx.set_state("a ˇ");
 6924    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6925    cx.assert_editor_state("a \"ˇ\"");
 6926
 6927    // Autoclose pair where the start and end characters are the same
 6928    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6929    cx.assert_editor_state("a \"\"ˇ");
 6930
 6931    // Don't autoclose when immediately after a word character
 6932    cx.set_state("");
 6933    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6934    cx.assert_editor_state("a\"ˇ");
 6935
 6936    // Do autoclose when after a non-word character
 6937    cx.set_state("");
 6938    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6939    cx.assert_editor_state("{\"ˇ\"");
 6940
 6941    // Non identical pairs autoclose regardless of preceding character
 6942    cx.set_state("");
 6943    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6944    cx.assert_editor_state("a{ˇ}");
 6945
 6946    // Don't autoclose pair if autoclose is disabled
 6947    cx.set_state("ˇ");
 6948    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6949    cx.assert_editor_state("");
 6950
 6951    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 6952    cx.set_state("«aˇ» b");
 6953    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6954    cx.assert_editor_state("<«aˇ»> b");
 6955}
 6956
 6957#[gpui::test]
 6958async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 6959    init_test(cx, |settings| {
 6960        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 6961    });
 6962
 6963    let mut cx = EditorTestContext::new(cx).await;
 6964
 6965    let language = Arc::new(Language::new(
 6966        LanguageConfig {
 6967            brackets: BracketPairConfig {
 6968                pairs: vec![
 6969                    BracketPair {
 6970                        start: "{".to_string(),
 6971                        end: "}".to_string(),
 6972                        close: true,
 6973                        surround: true,
 6974                        newline: true,
 6975                    },
 6976                    BracketPair {
 6977                        start: "(".to_string(),
 6978                        end: ")".to_string(),
 6979                        close: true,
 6980                        surround: true,
 6981                        newline: true,
 6982                    },
 6983                    BracketPair {
 6984                        start: "[".to_string(),
 6985                        end: "]".to_string(),
 6986                        close: false,
 6987                        surround: false,
 6988                        newline: true,
 6989                    },
 6990                ],
 6991                ..Default::default()
 6992            },
 6993            autoclose_before: "})]".to_string(),
 6994            ..Default::default()
 6995        },
 6996        Some(tree_sitter_rust::LANGUAGE.into()),
 6997    ));
 6998
 6999    cx.language_registry().add(language.clone());
 7000    cx.update_buffer(|buffer, cx| {
 7001        buffer.set_language(Some(language), cx);
 7002    });
 7003
 7004    cx.set_state(
 7005        &"
 7006            ˇ
 7007            ˇ
 7008            ˇ
 7009        "
 7010        .unindent(),
 7011    );
 7012
 7013    // ensure only matching closing brackets are skipped over
 7014    cx.update_editor(|editor, window, cx| {
 7015        editor.handle_input("}", window, cx);
 7016        editor.move_left(&MoveLeft, window, cx);
 7017        editor.handle_input(")", window, cx);
 7018        editor.move_left(&MoveLeft, window, cx);
 7019    });
 7020    cx.assert_editor_state(
 7021        &"
 7022            ˇ)}
 7023            ˇ)}
 7024            ˇ)}
 7025        "
 7026        .unindent(),
 7027    );
 7028
 7029    // skip-over closing brackets at multiple cursors
 7030    cx.update_editor(|editor, window, cx| {
 7031        editor.handle_input(")", window, cx);
 7032        editor.handle_input("}", window, cx);
 7033    });
 7034    cx.assert_editor_state(
 7035        &"
 7036            )}ˇ
 7037            )}ˇ
 7038            )}ˇ
 7039        "
 7040        .unindent(),
 7041    );
 7042
 7043    // ignore non-close brackets
 7044    cx.update_editor(|editor, window, cx| {
 7045        editor.handle_input("]", window, cx);
 7046        editor.move_left(&MoveLeft, window, cx);
 7047        editor.handle_input("]", window, cx);
 7048    });
 7049    cx.assert_editor_state(
 7050        &"
 7051            )}]ˇ]
 7052            )}]ˇ]
 7053            )}]ˇ]
 7054        "
 7055        .unindent(),
 7056    );
 7057}
 7058
 7059#[gpui::test]
 7060async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7061    init_test(cx, |_| {});
 7062
 7063    let mut cx = EditorTestContext::new(cx).await;
 7064
 7065    let html_language = Arc::new(
 7066        Language::new(
 7067            LanguageConfig {
 7068                name: "HTML".into(),
 7069                brackets: BracketPairConfig {
 7070                    pairs: vec![
 7071                        BracketPair {
 7072                            start: "<".into(),
 7073                            end: ">".into(),
 7074                            close: true,
 7075                            ..Default::default()
 7076                        },
 7077                        BracketPair {
 7078                            start: "{".into(),
 7079                            end: "}".into(),
 7080                            close: true,
 7081                            ..Default::default()
 7082                        },
 7083                        BracketPair {
 7084                            start: "(".into(),
 7085                            end: ")".into(),
 7086                            close: true,
 7087                            ..Default::default()
 7088                        },
 7089                    ],
 7090                    ..Default::default()
 7091                },
 7092                autoclose_before: "})]>".into(),
 7093                ..Default::default()
 7094            },
 7095            Some(tree_sitter_html::LANGUAGE.into()),
 7096        )
 7097        .with_injection_query(
 7098            r#"
 7099            (script_element
 7100                (raw_text) @injection.content
 7101                (#set! injection.language "javascript"))
 7102            "#,
 7103        )
 7104        .unwrap(),
 7105    );
 7106
 7107    let javascript_language = Arc::new(Language::new(
 7108        LanguageConfig {
 7109            name: "JavaScript".into(),
 7110            brackets: BracketPairConfig {
 7111                pairs: vec![
 7112                    BracketPair {
 7113                        start: "/*".into(),
 7114                        end: " */".into(),
 7115                        close: true,
 7116                        ..Default::default()
 7117                    },
 7118                    BracketPair {
 7119                        start: "{".into(),
 7120                        end: "}".into(),
 7121                        close: true,
 7122                        ..Default::default()
 7123                    },
 7124                    BracketPair {
 7125                        start: "(".into(),
 7126                        end: ")".into(),
 7127                        close: true,
 7128                        ..Default::default()
 7129                    },
 7130                ],
 7131                ..Default::default()
 7132            },
 7133            autoclose_before: "})]>".into(),
 7134            ..Default::default()
 7135        },
 7136        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7137    ));
 7138
 7139    cx.language_registry().add(html_language.clone());
 7140    cx.language_registry().add(javascript_language.clone());
 7141
 7142    cx.update_buffer(|buffer, cx| {
 7143        buffer.set_language(Some(html_language), cx);
 7144    });
 7145
 7146    cx.set_state(
 7147        &r#"
 7148            <body>ˇ
 7149                <script>
 7150                    var x = 1;ˇ
 7151                </script>
 7152            </body>ˇ
 7153        "#
 7154        .unindent(),
 7155    );
 7156
 7157    // Precondition: different languages are active at different locations.
 7158    cx.update_editor(|editor, window, cx| {
 7159        let snapshot = editor.snapshot(window, cx);
 7160        let cursors = editor.selections.ranges::<usize>(cx);
 7161        let languages = cursors
 7162            .iter()
 7163            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7164            .collect::<Vec<_>>();
 7165        assert_eq!(
 7166            languages,
 7167            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7168        );
 7169    });
 7170
 7171    // Angle brackets autoclose in HTML, but not JavaScript.
 7172    cx.update_editor(|editor, window, cx| {
 7173        editor.handle_input("<", window, cx);
 7174        editor.handle_input("a", window, cx);
 7175    });
 7176    cx.assert_editor_state(
 7177        &r#"
 7178            <body><aˇ>
 7179                <script>
 7180                    var x = 1;<aˇ
 7181                </script>
 7182            </body><aˇ>
 7183        "#
 7184        .unindent(),
 7185    );
 7186
 7187    // Curly braces and parens autoclose in both HTML and JavaScript.
 7188    cx.update_editor(|editor, window, cx| {
 7189        editor.handle_input(" b=", window, cx);
 7190        editor.handle_input("{", window, cx);
 7191        editor.handle_input("c", window, cx);
 7192        editor.handle_input("(", window, cx);
 7193    });
 7194    cx.assert_editor_state(
 7195        &r#"
 7196            <body><a b={c(ˇ)}>
 7197                <script>
 7198                    var x = 1;<a b={c(ˇ)}
 7199                </script>
 7200            </body><a b={c(ˇ)}>
 7201        "#
 7202        .unindent(),
 7203    );
 7204
 7205    // Brackets that were already autoclosed are skipped.
 7206    cx.update_editor(|editor, window, cx| {
 7207        editor.handle_input(")", window, cx);
 7208        editor.handle_input("d", window, cx);
 7209        editor.handle_input("}", window, cx);
 7210    });
 7211    cx.assert_editor_state(
 7212        &r#"
 7213            <body><a b={c()d}ˇ>
 7214                <script>
 7215                    var x = 1;<a b={c()d}ˇ
 7216                </script>
 7217            </body><a b={c()d}ˇ>
 7218        "#
 7219        .unindent(),
 7220    );
 7221    cx.update_editor(|editor, window, cx| {
 7222        editor.handle_input(">", window, cx);
 7223    });
 7224    cx.assert_editor_state(
 7225        &r#"
 7226            <body><a b={c()d}>ˇ
 7227                <script>
 7228                    var x = 1;<a b={c()d}>ˇ
 7229                </script>
 7230            </body><a b={c()d}>ˇ
 7231        "#
 7232        .unindent(),
 7233    );
 7234
 7235    // Reset
 7236    cx.set_state(
 7237        &r#"
 7238            <body>ˇ
 7239                <script>
 7240                    var x = 1;ˇ
 7241                </script>
 7242            </body>ˇ
 7243        "#
 7244        .unindent(),
 7245    );
 7246
 7247    cx.update_editor(|editor, window, cx| {
 7248        editor.handle_input("<", window, cx);
 7249    });
 7250    cx.assert_editor_state(
 7251        &r#"
 7252            <body><ˇ>
 7253                <script>
 7254                    var x = 1;<ˇ
 7255                </script>
 7256            </body><ˇ>
 7257        "#
 7258        .unindent(),
 7259    );
 7260
 7261    // When backspacing, the closing angle brackets are removed.
 7262    cx.update_editor(|editor, window, cx| {
 7263        editor.backspace(&Backspace, window, cx);
 7264    });
 7265    cx.assert_editor_state(
 7266        &r#"
 7267            <body>ˇ
 7268                <script>
 7269                    var x = 1;ˇ
 7270                </script>
 7271            </body>ˇ
 7272        "#
 7273        .unindent(),
 7274    );
 7275
 7276    // Block comments autoclose in JavaScript, but not HTML.
 7277    cx.update_editor(|editor, window, cx| {
 7278        editor.handle_input("/", window, cx);
 7279        editor.handle_input("*", window, cx);
 7280    });
 7281    cx.assert_editor_state(
 7282        &r#"
 7283            <body>/*ˇ
 7284                <script>
 7285                    var x = 1;/*ˇ */
 7286                </script>
 7287            </body>/*ˇ
 7288        "#
 7289        .unindent(),
 7290    );
 7291}
 7292
 7293#[gpui::test]
 7294async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7295    init_test(cx, |_| {});
 7296
 7297    let mut cx = EditorTestContext::new(cx).await;
 7298
 7299    let rust_language = Arc::new(
 7300        Language::new(
 7301            LanguageConfig {
 7302                name: "Rust".into(),
 7303                brackets: serde_json::from_value(json!([
 7304                    { "start": "{", "end": "}", "close": true, "newline": true },
 7305                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7306                ]))
 7307                .unwrap(),
 7308                autoclose_before: "})]>".into(),
 7309                ..Default::default()
 7310            },
 7311            Some(tree_sitter_rust::LANGUAGE.into()),
 7312        )
 7313        .with_override_query("(string_literal) @string")
 7314        .unwrap(),
 7315    );
 7316
 7317    cx.language_registry().add(rust_language.clone());
 7318    cx.update_buffer(|buffer, cx| {
 7319        buffer.set_language(Some(rust_language), cx);
 7320    });
 7321
 7322    cx.set_state(
 7323        &r#"
 7324            let x = ˇ
 7325        "#
 7326        .unindent(),
 7327    );
 7328
 7329    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7330    cx.update_editor(|editor, window, cx| {
 7331        editor.handle_input("\"", window, cx);
 7332    });
 7333    cx.assert_editor_state(
 7334        &r#"
 7335            let x = "ˇ"
 7336        "#
 7337        .unindent(),
 7338    );
 7339
 7340    // Inserting another quotation mark. The cursor moves across the existing
 7341    // automatically-inserted quotation mark.
 7342    cx.update_editor(|editor, window, cx| {
 7343        editor.handle_input("\"", window, cx);
 7344    });
 7345    cx.assert_editor_state(
 7346        &r#"
 7347            let x = ""ˇ
 7348        "#
 7349        .unindent(),
 7350    );
 7351
 7352    // Reset
 7353    cx.set_state(
 7354        &r#"
 7355            let x = ˇ
 7356        "#
 7357        .unindent(),
 7358    );
 7359
 7360    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7361    cx.update_editor(|editor, window, cx| {
 7362        editor.handle_input("\"", window, cx);
 7363        editor.handle_input(" ", window, cx);
 7364        editor.move_left(&Default::default(), window, cx);
 7365        editor.handle_input("\\", window, cx);
 7366        editor.handle_input("\"", window, cx);
 7367    });
 7368    cx.assert_editor_state(
 7369        &r#"
 7370            let x = "\"ˇ "
 7371        "#
 7372        .unindent(),
 7373    );
 7374
 7375    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7376    // mark. Nothing is inserted.
 7377    cx.update_editor(|editor, window, cx| {
 7378        editor.move_right(&Default::default(), window, cx);
 7379        editor.handle_input("\"", window, cx);
 7380    });
 7381    cx.assert_editor_state(
 7382        &r#"
 7383            let x = "\" "ˇ
 7384        "#
 7385        .unindent(),
 7386    );
 7387}
 7388
 7389#[gpui::test]
 7390async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7391    init_test(cx, |_| {});
 7392
 7393    let language = Arc::new(Language::new(
 7394        LanguageConfig {
 7395            brackets: BracketPairConfig {
 7396                pairs: vec![
 7397                    BracketPair {
 7398                        start: "{".to_string(),
 7399                        end: "}".to_string(),
 7400                        close: true,
 7401                        surround: true,
 7402                        newline: true,
 7403                    },
 7404                    BracketPair {
 7405                        start: "/* ".to_string(),
 7406                        end: "*/".to_string(),
 7407                        close: true,
 7408                        surround: true,
 7409                        ..Default::default()
 7410                    },
 7411                ],
 7412                ..Default::default()
 7413            },
 7414            ..Default::default()
 7415        },
 7416        Some(tree_sitter_rust::LANGUAGE.into()),
 7417    ));
 7418
 7419    let text = r#"
 7420        a
 7421        b
 7422        c
 7423    "#
 7424    .unindent();
 7425
 7426    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7427    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7428    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7429    editor
 7430        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7431        .await;
 7432
 7433    editor.update_in(cx, |editor, window, cx| {
 7434        editor.change_selections(None, window, cx, |s| {
 7435            s.select_display_ranges([
 7436                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7437                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7438                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7439            ])
 7440        });
 7441
 7442        editor.handle_input("{", window, cx);
 7443        editor.handle_input("{", window, cx);
 7444        editor.handle_input("{", window, cx);
 7445        assert_eq!(
 7446            editor.text(cx),
 7447            "
 7448                {{{a}}}
 7449                {{{b}}}
 7450                {{{c}}}
 7451            "
 7452            .unindent()
 7453        );
 7454        assert_eq!(
 7455            editor.selections.display_ranges(cx),
 7456            [
 7457                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7458                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7459                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7460            ]
 7461        );
 7462
 7463        editor.undo(&Undo, window, cx);
 7464        editor.undo(&Undo, window, cx);
 7465        editor.undo(&Undo, window, cx);
 7466        assert_eq!(
 7467            editor.text(cx),
 7468            "
 7469                a
 7470                b
 7471                c
 7472            "
 7473            .unindent()
 7474        );
 7475        assert_eq!(
 7476            editor.selections.display_ranges(cx),
 7477            [
 7478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7479                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7480                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7481            ]
 7482        );
 7483
 7484        // Ensure inserting the first character of a multi-byte bracket pair
 7485        // doesn't surround the selections with the bracket.
 7486        editor.handle_input("/", window, cx);
 7487        assert_eq!(
 7488            editor.text(cx),
 7489            "
 7490                /
 7491                /
 7492                /
 7493            "
 7494            .unindent()
 7495        );
 7496        assert_eq!(
 7497            editor.selections.display_ranges(cx),
 7498            [
 7499                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7500                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7501                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7502            ]
 7503        );
 7504
 7505        editor.undo(&Undo, window, cx);
 7506        assert_eq!(
 7507            editor.text(cx),
 7508            "
 7509                a
 7510                b
 7511                c
 7512            "
 7513            .unindent()
 7514        );
 7515        assert_eq!(
 7516            editor.selections.display_ranges(cx),
 7517            [
 7518                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7519                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7520                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7521            ]
 7522        );
 7523
 7524        // Ensure inserting the last character of a multi-byte bracket pair
 7525        // doesn't surround the selections with the bracket.
 7526        editor.handle_input("*", window, cx);
 7527        assert_eq!(
 7528            editor.text(cx),
 7529            "
 7530                *
 7531                *
 7532                *
 7533            "
 7534            .unindent()
 7535        );
 7536        assert_eq!(
 7537            editor.selections.display_ranges(cx),
 7538            [
 7539                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7540                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7541                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7542            ]
 7543        );
 7544    });
 7545}
 7546
 7547#[gpui::test]
 7548async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7549    init_test(cx, |_| {});
 7550
 7551    let language = Arc::new(Language::new(
 7552        LanguageConfig {
 7553            brackets: BracketPairConfig {
 7554                pairs: vec![BracketPair {
 7555                    start: "{".to_string(),
 7556                    end: "}".to_string(),
 7557                    close: true,
 7558                    surround: true,
 7559                    newline: true,
 7560                }],
 7561                ..Default::default()
 7562            },
 7563            autoclose_before: "}".to_string(),
 7564            ..Default::default()
 7565        },
 7566        Some(tree_sitter_rust::LANGUAGE.into()),
 7567    ));
 7568
 7569    let text = r#"
 7570        a
 7571        b
 7572        c
 7573    "#
 7574    .unindent();
 7575
 7576    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7577    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7578    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7579    editor
 7580        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7581        .await;
 7582
 7583    editor.update_in(cx, |editor, window, cx| {
 7584        editor.change_selections(None, window, cx, |s| {
 7585            s.select_ranges([
 7586                Point::new(0, 1)..Point::new(0, 1),
 7587                Point::new(1, 1)..Point::new(1, 1),
 7588                Point::new(2, 1)..Point::new(2, 1),
 7589            ])
 7590        });
 7591
 7592        editor.handle_input("{", window, cx);
 7593        editor.handle_input("{", window, cx);
 7594        editor.handle_input("_", window, cx);
 7595        assert_eq!(
 7596            editor.text(cx),
 7597            "
 7598                a{{_}}
 7599                b{{_}}
 7600                c{{_}}
 7601            "
 7602            .unindent()
 7603        );
 7604        assert_eq!(
 7605            editor.selections.ranges::<Point>(cx),
 7606            [
 7607                Point::new(0, 4)..Point::new(0, 4),
 7608                Point::new(1, 4)..Point::new(1, 4),
 7609                Point::new(2, 4)..Point::new(2, 4)
 7610            ]
 7611        );
 7612
 7613        editor.backspace(&Default::default(), window, cx);
 7614        editor.backspace(&Default::default(), window, cx);
 7615        assert_eq!(
 7616            editor.text(cx),
 7617            "
 7618                a{}
 7619                b{}
 7620                c{}
 7621            "
 7622            .unindent()
 7623        );
 7624        assert_eq!(
 7625            editor.selections.ranges::<Point>(cx),
 7626            [
 7627                Point::new(0, 2)..Point::new(0, 2),
 7628                Point::new(1, 2)..Point::new(1, 2),
 7629                Point::new(2, 2)..Point::new(2, 2)
 7630            ]
 7631        );
 7632
 7633        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 7634        assert_eq!(
 7635            editor.text(cx),
 7636            "
 7637                a
 7638                b
 7639                c
 7640            "
 7641            .unindent()
 7642        );
 7643        assert_eq!(
 7644            editor.selections.ranges::<Point>(cx),
 7645            [
 7646                Point::new(0, 1)..Point::new(0, 1),
 7647                Point::new(1, 1)..Point::new(1, 1),
 7648                Point::new(2, 1)..Point::new(2, 1)
 7649            ]
 7650        );
 7651    });
 7652}
 7653
 7654#[gpui::test]
 7655async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 7656    init_test(cx, |settings| {
 7657        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7658    });
 7659
 7660    let mut cx = EditorTestContext::new(cx).await;
 7661
 7662    let language = Arc::new(Language::new(
 7663        LanguageConfig {
 7664            brackets: BracketPairConfig {
 7665                pairs: vec![
 7666                    BracketPair {
 7667                        start: "{".to_string(),
 7668                        end: "}".to_string(),
 7669                        close: true,
 7670                        surround: true,
 7671                        newline: true,
 7672                    },
 7673                    BracketPair {
 7674                        start: "(".to_string(),
 7675                        end: ")".to_string(),
 7676                        close: true,
 7677                        surround: true,
 7678                        newline: true,
 7679                    },
 7680                    BracketPair {
 7681                        start: "[".to_string(),
 7682                        end: "]".to_string(),
 7683                        close: false,
 7684                        surround: true,
 7685                        newline: true,
 7686                    },
 7687                ],
 7688                ..Default::default()
 7689            },
 7690            autoclose_before: "})]".to_string(),
 7691            ..Default::default()
 7692        },
 7693        Some(tree_sitter_rust::LANGUAGE.into()),
 7694    ));
 7695
 7696    cx.language_registry().add(language.clone());
 7697    cx.update_buffer(|buffer, cx| {
 7698        buffer.set_language(Some(language), cx);
 7699    });
 7700
 7701    cx.set_state(
 7702        &"
 7703            {(ˇ)}
 7704            [[ˇ]]
 7705            {(ˇ)}
 7706        "
 7707        .unindent(),
 7708    );
 7709
 7710    cx.update_editor(|editor, window, cx| {
 7711        editor.backspace(&Default::default(), window, cx);
 7712        editor.backspace(&Default::default(), window, cx);
 7713    });
 7714
 7715    cx.assert_editor_state(
 7716        &"
 7717            ˇ
 7718            ˇ]]
 7719            ˇ
 7720        "
 7721        .unindent(),
 7722    );
 7723
 7724    cx.update_editor(|editor, window, cx| {
 7725        editor.handle_input("{", window, cx);
 7726        editor.handle_input("{", window, cx);
 7727        editor.move_right(&MoveRight, window, cx);
 7728        editor.move_right(&MoveRight, window, cx);
 7729        editor.move_left(&MoveLeft, window, cx);
 7730        editor.move_left(&MoveLeft, window, cx);
 7731        editor.backspace(&Default::default(), window, cx);
 7732    });
 7733
 7734    cx.assert_editor_state(
 7735        &"
 7736            {ˇ}
 7737            {ˇ}]]
 7738            {ˇ}
 7739        "
 7740        .unindent(),
 7741    );
 7742
 7743    cx.update_editor(|editor, window, cx| {
 7744        editor.backspace(&Default::default(), window, cx);
 7745    });
 7746
 7747    cx.assert_editor_state(
 7748        &"
 7749            ˇ
 7750            ˇ]]
 7751            ˇ
 7752        "
 7753        .unindent(),
 7754    );
 7755}
 7756
 7757#[gpui::test]
 7758async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 7759    init_test(cx, |_| {});
 7760
 7761    let language = Arc::new(Language::new(
 7762        LanguageConfig::default(),
 7763        Some(tree_sitter_rust::LANGUAGE.into()),
 7764    ));
 7765
 7766    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 7767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7768    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7769    editor
 7770        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7771        .await;
 7772
 7773    editor.update_in(cx, |editor, window, cx| {
 7774        editor.set_auto_replace_emoji_shortcode(true);
 7775
 7776        editor.handle_input("Hello ", window, cx);
 7777        editor.handle_input(":wave", window, cx);
 7778        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 7779
 7780        editor.handle_input(":", window, cx);
 7781        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 7782
 7783        editor.handle_input(" :smile", window, cx);
 7784        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 7785
 7786        editor.handle_input(":", window, cx);
 7787        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 7788
 7789        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 7790        editor.handle_input(":wave", window, cx);
 7791        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 7792
 7793        editor.handle_input(":", window, cx);
 7794        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 7795
 7796        editor.handle_input(":1", window, cx);
 7797        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 7798
 7799        editor.handle_input(":", window, cx);
 7800        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 7801
 7802        // Ensure shortcode does not get replaced when it is part of a word
 7803        editor.handle_input(" Test:wave", window, cx);
 7804        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 7805
 7806        editor.handle_input(":", window, cx);
 7807        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 7808
 7809        editor.set_auto_replace_emoji_shortcode(false);
 7810
 7811        // Ensure shortcode does not get replaced when auto replace is off
 7812        editor.handle_input(" :wave", window, cx);
 7813        assert_eq!(
 7814            editor.text(cx),
 7815            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 7816        );
 7817
 7818        editor.handle_input(":", window, cx);
 7819        assert_eq!(
 7820            editor.text(cx),
 7821            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 7822        );
 7823    });
 7824}
 7825
 7826#[gpui::test]
 7827async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 7828    init_test(cx, |_| {});
 7829
 7830    let (text, insertion_ranges) = marked_text_ranges(
 7831        indoc! {"
 7832            ˇ
 7833        "},
 7834        false,
 7835    );
 7836
 7837    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7838    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7839
 7840    _ = editor.update_in(cx, |editor, window, cx| {
 7841        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 7842
 7843        editor
 7844            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7845            .unwrap();
 7846
 7847        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7848            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7849            assert_eq!(editor.text(cx), expected_text);
 7850            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7851        }
 7852
 7853        assert(
 7854            editor,
 7855            cx,
 7856            indoc! {"
 7857            type «» =•
 7858            "},
 7859        );
 7860
 7861        assert!(editor.context_menu_visible(), "There should be a matches");
 7862    });
 7863}
 7864
 7865#[gpui::test]
 7866async fn test_snippets(cx: &mut TestAppContext) {
 7867    init_test(cx, |_| {});
 7868
 7869    let (text, insertion_ranges) = marked_text_ranges(
 7870        indoc! {"
 7871            a.ˇ b
 7872            a.ˇ b
 7873            a.ˇ b
 7874        "},
 7875        false,
 7876    );
 7877
 7878    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7879    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7880
 7881    editor.update_in(cx, |editor, window, cx| {
 7882        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 7883
 7884        editor
 7885            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7886            .unwrap();
 7887
 7888        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7889            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7890            assert_eq!(editor.text(cx), expected_text);
 7891            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7892        }
 7893
 7894        assert(
 7895            editor,
 7896            cx,
 7897            indoc! {"
 7898                a.f(«one», two, «three») b
 7899                a.f(«one», two, «three») b
 7900                a.f(«one», two, «three») b
 7901            "},
 7902        );
 7903
 7904        // Can't move earlier than the first tab stop
 7905        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 7906        assert(
 7907            editor,
 7908            cx,
 7909            indoc! {"
 7910                a.f(«one», two, «three») b
 7911                a.f(«one», two, «three») b
 7912                a.f(«one», two, «three») b
 7913            "},
 7914        );
 7915
 7916        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7917        assert(
 7918            editor,
 7919            cx,
 7920            indoc! {"
 7921                a.f(one, «two», three) b
 7922                a.f(one, «two», three) b
 7923                a.f(one, «two», three) b
 7924            "},
 7925        );
 7926
 7927        editor.move_to_prev_snippet_tabstop(window, cx);
 7928        assert(
 7929            editor,
 7930            cx,
 7931            indoc! {"
 7932                a.f(«one», two, «three») b
 7933                a.f(«one», two, «three») b
 7934                a.f(«one», two, «three») b
 7935            "},
 7936        );
 7937
 7938        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7939        assert(
 7940            editor,
 7941            cx,
 7942            indoc! {"
 7943                a.f(one, «two», three) b
 7944                a.f(one, «two», three) b
 7945                a.f(one, «two», three) b
 7946            "},
 7947        );
 7948        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7949        assert(
 7950            editor,
 7951            cx,
 7952            indoc! {"
 7953                a.f(one, two, three)ˇ b
 7954                a.f(one, two, three)ˇ b
 7955                a.f(one, two, three)ˇ b
 7956            "},
 7957        );
 7958
 7959        // As soon as the last tab stop is reached, snippet state is gone
 7960        editor.move_to_prev_snippet_tabstop(window, cx);
 7961        assert(
 7962            editor,
 7963            cx,
 7964            indoc! {"
 7965                a.f(one, two, three)ˇ b
 7966                a.f(one, two, three)ˇ b
 7967                a.f(one, two, three)ˇ b
 7968            "},
 7969        );
 7970    });
 7971}
 7972
 7973#[gpui::test]
 7974async fn test_document_format_during_save(cx: &mut TestAppContext) {
 7975    init_test(cx, |_| {});
 7976
 7977    let fs = FakeFs::new(cx.executor());
 7978    fs.insert_file(path!("/file.rs"), Default::default()).await;
 7979
 7980    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 7981
 7982    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7983    language_registry.add(rust_lang());
 7984    let mut fake_servers = language_registry.register_fake_lsp(
 7985        "Rust",
 7986        FakeLspAdapter {
 7987            capabilities: lsp::ServerCapabilities {
 7988                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7989                ..Default::default()
 7990            },
 7991            ..Default::default()
 7992        },
 7993    );
 7994
 7995    let buffer = project
 7996        .update(cx, |project, cx| {
 7997            project.open_local_buffer(path!("/file.rs"), cx)
 7998        })
 7999        .await
 8000        .unwrap();
 8001
 8002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8003    let (editor, cx) = cx.add_window_view(|window, cx| {
 8004        build_editor_with_project(project.clone(), buffer, window, cx)
 8005    });
 8006    editor.update_in(cx, |editor, window, cx| {
 8007        editor.set_text("one\ntwo\nthree\n", window, cx)
 8008    });
 8009    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8010
 8011    cx.executor().start_waiting();
 8012    let fake_server = fake_servers.next().await.unwrap();
 8013
 8014    {
 8015        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8016            move |params, _| async move {
 8017                assert_eq!(
 8018                    params.text_document.uri,
 8019                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8020                );
 8021                assert_eq!(params.options.tab_size, 4);
 8022                Ok(Some(vec![lsp::TextEdit::new(
 8023                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8024                    ", ".to_string(),
 8025                )]))
 8026            },
 8027        );
 8028        let save = editor
 8029            .update_in(cx, |editor, window, cx| {
 8030                editor.save(true, project.clone(), window, cx)
 8031            })
 8032            .unwrap();
 8033        cx.executor().start_waiting();
 8034        save.await;
 8035
 8036        assert_eq!(
 8037            editor.update(cx, |editor, cx| editor.text(cx)),
 8038            "one, two\nthree\n"
 8039        );
 8040        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8041    }
 8042
 8043    {
 8044        editor.update_in(cx, |editor, window, cx| {
 8045            editor.set_text("one\ntwo\nthree\n", window, cx)
 8046        });
 8047        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8048
 8049        // Ensure we can still save even if formatting hangs.
 8050        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8051            move |params, _| async move {
 8052                assert_eq!(
 8053                    params.text_document.uri,
 8054                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8055                );
 8056                futures::future::pending::<()>().await;
 8057                unreachable!()
 8058            },
 8059        );
 8060        let save = editor
 8061            .update_in(cx, |editor, window, cx| {
 8062                editor.save(true, project.clone(), window, cx)
 8063            })
 8064            .unwrap();
 8065        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8066        cx.executor().start_waiting();
 8067        save.await;
 8068        assert_eq!(
 8069            editor.update(cx, |editor, cx| editor.text(cx)),
 8070            "one\ntwo\nthree\n"
 8071        );
 8072    }
 8073
 8074    // For non-dirty buffer, no formatting request should be sent
 8075    {
 8076        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8077
 8078        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8079            panic!("Should not be invoked on non-dirty buffer");
 8080        });
 8081        let save = editor
 8082            .update_in(cx, |editor, window, cx| {
 8083                editor.save(true, project.clone(), window, cx)
 8084            })
 8085            .unwrap();
 8086        cx.executor().start_waiting();
 8087        save.await;
 8088    }
 8089
 8090    // Set rust language override and assert overridden tabsize is sent to language server
 8091    update_test_language_settings(cx, |settings| {
 8092        settings.languages.insert(
 8093            "Rust".into(),
 8094            LanguageSettingsContent {
 8095                tab_size: NonZeroU32::new(8),
 8096                ..Default::default()
 8097            },
 8098        );
 8099    });
 8100
 8101    {
 8102        editor.update_in(cx, |editor, window, cx| {
 8103            editor.set_text("somehting_new\n", window, cx)
 8104        });
 8105        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8106        let _formatting_request_signal = fake_server
 8107            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8108                assert_eq!(
 8109                    params.text_document.uri,
 8110                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8111                );
 8112                assert_eq!(params.options.tab_size, 8);
 8113                Ok(Some(vec![]))
 8114            });
 8115        let save = editor
 8116            .update_in(cx, |editor, window, cx| {
 8117                editor.save(true, project.clone(), window, cx)
 8118            })
 8119            .unwrap();
 8120        cx.executor().start_waiting();
 8121        save.await;
 8122    }
 8123}
 8124
 8125#[gpui::test]
 8126async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8127    init_test(cx, |_| {});
 8128
 8129    let cols = 4;
 8130    let rows = 10;
 8131    let sample_text_1 = sample_text(rows, cols, 'a');
 8132    assert_eq!(
 8133        sample_text_1,
 8134        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8135    );
 8136    let sample_text_2 = sample_text(rows, cols, 'l');
 8137    assert_eq!(
 8138        sample_text_2,
 8139        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8140    );
 8141    let sample_text_3 = sample_text(rows, cols, 'v');
 8142    assert_eq!(
 8143        sample_text_3,
 8144        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8145    );
 8146
 8147    let fs = FakeFs::new(cx.executor());
 8148    fs.insert_tree(
 8149        path!("/a"),
 8150        json!({
 8151            "main.rs": sample_text_1,
 8152            "other.rs": sample_text_2,
 8153            "lib.rs": sample_text_3,
 8154        }),
 8155    )
 8156    .await;
 8157
 8158    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8159    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8160    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8161
 8162    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8163    language_registry.add(rust_lang());
 8164    let mut fake_servers = language_registry.register_fake_lsp(
 8165        "Rust",
 8166        FakeLspAdapter {
 8167            capabilities: lsp::ServerCapabilities {
 8168                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8169                ..Default::default()
 8170            },
 8171            ..Default::default()
 8172        },
 8173    );
 8174
 8175    let worktree = project.update(cx, |project, cx| {
 8176        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8177        assert_eq!(worktrees.len(), 1);
 8178        worktrees.pop().unwrap()
 8179    });
 8180    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8181
 8182    let buffer_1 = project
 8183        .update(cx, |project, cx| {
 8184            project.open_buffer((worktree_id, "main.rs"), cx)
 8185        })
 8186        .await
 8187        .unwrap();
 8188    let buffer_2 = project
 8189        .update(cx, |project, cx| {
 8190            project.open_buffer((worktree_id, "other.rs"), cx)
 8191        })
 8192        .await
 8193        .unwrap();
 8194    let buffer_3 = project
 8195        .update(cx, |project, cx| {
 8196            project.open_buffer((worktree_id, "lib.rs"), cx)
 8197        })
 8198        .await
 8199        .unwrap();
 8200
 8201    let multi_buffer = cx.new(|cx| {
 8202        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8203        multi_buffer.push_excerpts(
 8204            buffer_1.clone(),
 8205            [
 8206                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8207                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8208                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8209            ],
 8210            cx,
 8211        );
 8212        multi_buffer.push_excerpts(
 8213            buffer_2.clone(),
 8214            [
 8215                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8216                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8217                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8218            ],
 8219            cx,
 8220        );
 8221        multi_buffer.push_excerpts(
 8222            buffer_3.clone(),
 8223            [
 8224                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8225                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8226                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8227            ],
 8228            cx,
 8229        );
 8230        multi_buffer
 8231    });
 8232    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8233        Editor::new(
 8234            EditorMode::full(),
 8235            multi_buffer,
 8236            Some(project.clone()),
 8237            window,
 8238            cx,
 8239        )
 8240    });
 8241
 8242    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8243        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8244            s.select_ranges(Some(1..2))
 8245        });
 8246        editor.insert("|one|two|three|", window, cx);
 8247    });
 8248    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8249    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8250        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8251            s.select_ranges(Some(60..70))
 8252        });
 8253        editor.insert("|four|five|six|", window, cx);
 8254    });
 8255    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8256
 8257    // First two buffers should be edited, but not the third one.
 8258    assert_eq!(
 8259        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8260        "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}",
 8261    );
 8262    buffer_1.update(cx, |buffer, _| {
 8263        assert!(buffer.is_dirty());
 8264        assert_eq!(
 8265            buffer.text(),
 8266            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8267        )
 8268    });
 8269    buffer_2.update(cx, |buffer, _| {
 8270        assert!(buffer.is_dirty());
 8271        assert_eq!(
 8272            buffer.text(),
 8273            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8274        )
 8275    });
 8276    buffer_3.update(cx, |buffer, _| {
 8277        assert!(!buffer.is_dirty());
 8278        assert_eq!(buffer.text(), sample_text_3,)
 8279    });
 8280    cx.executor().run_until_parked();
 8281
 8282    cx.executor().start_waiting();
 8283    let save = multi_buffer_editor
 8284        .update_in(cx, |editor, window, cx| {
 8285            editor.save(true, project.clone(), window, cx)
 8286        })
 8287        .unwrap();
 8288
 8289    let fake_server = fake_servers.next().await.unwrap();
 8290    fake_server
 8291        .server
 8292        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8293            Ok(Some(vec![lsp::TextEdit::new(
 8294                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8295                format!("[{} formatted]", params.text_document.uri),
 8296            )]))
 8297        })
 8298        .detach();
 8299    save.await;
 8300
 8301    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8302    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8303    assert_eq!(
 8304        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8305        uri!(
 8306            "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}"
 8307        ),
 8308    );
 8309    buffer_1.update(cx, |buffer, _| {
 8310        assert!(!buffer.is_dirty());
 8311        assert_eq!(
 8312            buffer.text(),
 8313            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8314        )
 8315    });
 8316    buffer_2.update(cx, |buffer, _| {
 8317        assert!(!buffer.is_dirty());
 8318        assert_eq!(
 8319            buffer.text(),
 8320            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8321        )
 8322    });
 8323    buffer_3.update(cx, |buffer, _| {
 8324        assert!(!buffer.is_dirty());
 8325        assert_eq!(buffer.text(), sample_text_3,)
 8326    });
 8327}
 8328
 8329#[gpui::test]
 8330async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8331    init_test(cx, |_| {});
 8332
 8333    let fs = FakeFs::new(cx.executor());
 8334    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8335
 8336    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8337
 8338    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8339    language_registry.add(rust_lang());
 8340    let mut fake_servers = language_registry.register_fake_lsp(
 8341        "Rust",
 8342        FakeLspAdapter {
 8343            capabilities: lsp::ServerCapabilities {
 8344                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8345                ..Default::default()
 8346            },
 8347            ..Default::default()
 8348        },
 8349    );
 8350
 8351    let buffer = project
 8352        .update(cx, |project, cx| {
 8353            project.open_local_buffer(path!("/file.rs"), cx)
 8354        })
 8355        .await
 8356        .unwrap();
 8357
 8358    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8359    let (editor, cx) = cx.add_window_view(|window, cx| {
 8360        build_editor_with_project(project.clone(), buffer, window, cx)
 8361    });
 8362    editor.update_in(cx, |editor, window, cx| {
 8363        editor.set_text("one\ntwo\nthree\n", window, cx)
 8364    });
 8365    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8366
 8367    cx.executor().start_waiting();
 8368    let fake_server = fake_servers.next().await.unwrap();
 8369
 8370    let save = editor
 8371        .update_in(cx, |editor, window, cx| {
 8372            editor.save(true, project.clone(), window, cx)
 8373        })
 8374        .unwrap();
 8375    fake_server
 8376        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8377            assert_eq!(
 8378                params.text_document.uri,
 8379                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8380            );
 8381            assert_eq!(params.options.tab_size, 4);
 8382            Ok(Some(vec![lsp::TextEdit::new(
 8383                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8384                ", ".to_string(),
 8385            )]))
 8386        })
 8387        .next()
 8388        .await;
 8389    cx.executor().start_waiting();
 8390    save.await;
 8391    assert_eq!(
 8392        editor.update(cx, |editor, cx| editor.text(cx)),
 8393        "one, two\nthree\n"
 8394    );
 8395    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8396
 8397    editor.update_in(cx, |editor, window, cx| {
 8398        editor.set_text("one\ntwo\nthree\n", window, cx)
 8399    });
 8400    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8401
 8402    // Ensure we can still save even if formatting hangs.
 8403    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8404        move |params, _| async move {
 8405            assert_eq!(
 8406                params.text_document.uri,
 8407                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8408            );
 8409            futures::future::pending::<()>().await;
 8410            unreachable!()
 8411        },
 8412    );
 8413    let save = editor
 8414        .update_in(cx, |editor, window, cx| {
 8415            editor.save(true, project.clone(), window, cx)
 8416        })
 8417        .unwrap();
 8418    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8419    cx.executor().start_waiting();
 8420    save.await;
 8421    assert_eq!(
 8422        editor.update(cx, |editor, cx| editor.text(cx)),
 8423        "one\ntwo\nthree\n"
 8424    );
 8425    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8426
 8427    // For non-dirty buffer, no formatting request should be sent
 8428    let save = editor
 8429        .update_in(cx, |editor, window, cx| {
 8430            editor.save(true, project.clone(), window, cx)
 8431        })
 8432        .unwrap();
 8433    let _pending_format_request = fake_server
 8434        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8435            panic!("Should not be invoked on non-dirty buffer");
 8436        })
 8437        .next();
 8438    cx.executor().start_waiting();
 8439    save.await;
 8440
 8441    // Set Rust language override and assert overridden tabsize is sent to language server
 8442    update_test_language_settings(cx, |settings| {
 8443        settings.languages.insert(
 8444            "Rust".into(),
 8445            LanguageSettingsContent {
 8446                tab_size: NonZeroU32::new(8),
 8447                ..Default::default()
 8448            },
 8449        );
 8450    });
 8451
 8452    editor.update_in(cx, |editor, window, cx| {
 8453        editor.set_text("somehting_new\n", window, cx)
 8454    });
 8455    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8456    let save = editor
 8457        .update_in(cx, |editor, window, cx| {
 8458            editor.save(true, project.clone(), window, cx)
 8459        })
 8460        .unwrap();
 8461    fake_server
 8462        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8463            assert_eq!(
 8464                params.text_document.uri,
 8465                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8466            );
 8467            assert_eq!(params.options.tab_size, 8);
 8468            Ok(Some(vec![]))
 8469        })
 8470        .next()
 8471        .await;
 8472    cx.executor().start_waiting();
 8473    save.await;
 8474}
 8475
 8476#[gpui::test]
 8477async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8478    init_test(cx, |settings| {
 8479        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8480            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8481        ))
 8482    });
 8483
 8484    let fs = FakeFs::new(cx.executor());
 8485    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8486
 8487    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8488
 8489    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8490    language_registry.add(Arc::new(Language::new(
 8491        LanguageConfig {
 8492            name: "Rust".into(),
 8493            matcher: LanguageMatcher {
 8494                path_suffixes: vec!["rs".to_string()],
 8495                ..Default::default()
 8496            },
 8497            ..LanguageConfig::default()
 8498        },
 8499        Some(tree_sitter_rust::LANGUAGE.into()),
 8500    )));
 8501    update_test_language_settings(cx, |settings| {
 8502        // Enable Prettier formatting for the same buffer, and ensure
 8503        // LSP is called instead of Prettier.
 8504        settings.defaults.prettier = Some(PrettierSettings {
 8505            allowed: true,
 8506            ..PrettierSettings::default()
 8507        });
 8508    });
 8509    let mut fake_servers = language_registry.register_fake_lsp(
 8510        "Rust",
 8511        FakeLspAdapter {
 8512            capabilities: lsp::ServerCapabilities {
 8513                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8514                ..Default::default()
 8515            },
 8516            ..Default::default()
 8517        },
 8518    );
 8519
 8520    let buffer = project
 8521        .update(cx, |project, cx| {
 8522            project.open_local_buffer(path!("/file.rs"), cx)
 8523        })
 8524        .await
 8525        .unwrap();
 8526
 8527    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8528    let (editor, cx) = cx.add_window_view(|window, cx| {
 8529        build_editor_with_project(project.clone(), buffer, window, cx)
 8530    });
 8531    editor.update_in(cx, |editor, window, cx| {
 8532        editor.set_text("one\ntwo\nthree\n", window, cx)
 8533    });
 8534
 8535    cx.executor().start_waiting();
 8536    let fake_server = fake_servers.next().await.unwrap();
 8537
 8538    let format = editor
 8539        .update_in(cx, |editor, window, cx| {
 8540            editor.perform_format(
 8541                project.clone(),
 8542                FormatTrigger::Manual,
 8543                FormatTarget::Buffers,
 8544                window,
 8545                cx,
 8546            )
 8547        })
 8548        .unwrap();
 8549    fake_server
 8550        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8551            assert_eq!(
 8552                params.text_document.uri,
 8553                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8554            );
 8555            assert_eq!(params.options.tab_size, 4);
 8556            Ok(Some(vec![lsp::TextEdit::new(
 8557                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8558                ", ".to_string(),
 8559            )]))
 8560        })
 8561        .next()
 8562        .await;
 8563    cx.executor().start_waiting();
 8564    format.await;
 8565    assert_eq!(
 8566        editor.update(cx, |editor, cx| editor.text(cx)),
 8567        "one, two\nthree\n"
 8568    );
 8569
 8570    editor.update_in(cx, |editor, window, cx| {
 8571        editor.set_text("one\ntwo\nthree\n", window, cx)
 8572    });
 8573    // Ensure we don't lock if formatting hangs.
 8574    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8575        move |params, _| async move {
 8576            assert_eq!(
 8577                params.text_document.uri,
 8578                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8579            );
 8580            futures::future::pending::<()>().await;
 8581            unreachable!()
 8582        },
 8583    );
 8584    let format = editor
 8585        .update_in(cx, |editor, window, cx| {
 8586            editor.perform_format(
 8587                project,
 8588                FormatTrigger::Manual,
 8589                FormatTarget::Buffers,
 8590                window,
 8591                cx,
 8592            )
 8593        })
 8594        .unwrap();
 8595    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8596    cx.executor().start_waiting();
 8597    format.await;
 8598    assert_eq!(
 8599        editor.update(cx, |editor, cx| editor.text(cx)),
 8600        "one\ntwo\nthree\n"
 8601    );
 8602}
 8603
 8604#[gpui::test]
 8605async fn test_multiple_formatters(cx: &mut TestAppContext) {
 8606    init_test(cx, |settings| {
 8607        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 8608        settings.defaults.formatter =
 8609            Some(language_settings::SelectedFormatter::List(FormatterList(
 8610                vec![
 8611                    Formatter::LanguageServer { name: None },
 8612                    Formatter::CodeActions(
 8613                        [
 8614                            ("code-action-1".into(), true),
 8615                            ("code-action-2".into(), true),
 8616                        ]
 8617                        .into_iter()
 8618                        .collect(),
 8619                    ),
 8620                ]
 8621                .into(),
 8622            )))
 8623    });
 8624
 8625    let fs = FakeFs::new(cx.executor());
 8626    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 8627        .await;
 8628
 8629    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8630    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8631    language_registry.add(rust_lang());
 8632
 8633    let mut fake_servers = language_registry.register_fake_lsp(
 8634        "Rust",
 8635        FakeLspAdapter {
 8636            capabilities: lsp::ServerCapabilities {
 8637                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8638                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 8639                    commands: vec!["the-command-for-code-action-1".into()],
 8640                    ..Default::default()
 8641                }),
 8642                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8643                ..Default::default()
 8644            },
 8645            ..Default::default()
 8646        },
 8647    );
 8648
 8649    let buffer = project
 8650        .update(cx, |project, cx| {
 8651            project.open_local_buffer(path!("/file.rs"), cx)
 8652        })
 8653        .await
 8654        .unwrap();
 8655
 8656    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8657    let (editor, cx) = cx.add_window_view(|window, cx| {
 8658        build_editor_with_project(project.clone(), buffer, window, cx)
 8659    });
 8660
 8661    cx.executor().start_waiting();
 8662
 8663    let fake_server = fake_servers.next().await.unwrap();
 8664    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8665        move |_params, _| async move {
 8666            Ok(Some(vec![lsp::TextEdit::new(
 8667                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8668                "applied-formatting\n".to_string(),
 8669            )]))
 8670        },
 8671    );
 8672    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8673        move |params, _| async move {
 8674            assert_eq!(
 8675                params.context.only,
 8676                Some(vec!["code-action-1".into(), "code-action-2".into()])
 8677            );
 8678            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 8679            Ok(Some(vec![
 8680                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8681                    kind: Some("code-action-1".into()),
 8682                    edit: Some(lsp::WorkspaceEdit::new(
 8683                        [(
 8684                            uri.clone(),
 8685                            vec![lsp::TextEdit::new(
 8686                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8687                                "applied-code-action-1-edit\n".to_string(),
 8688                            )],
 8689                        )]
 8690                        .into_iter()
 8691                        .collect(),
 8692                    )),
 8693                    command: Some(lsp::Command {
 8694                        command: "the-command-for-code-action-1".into(),
 8695                        ..Default::default()
 8696                    }),
 8697                    ..Default::default()
 8698                }),
 8699                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8700                    kind: Some("code-action-2".into()),
 8701                    edit: Some(lsp::WorkspaceEdit::new(
 8702                        [(
 8703                            uri.clone(),
 8704                            vec![lsp::TextEdit::new(
 8705                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8706                                "applied-code-action-2-edit\n".to_string(),
 8707                            )],
 8708                        )]
 8709                        .into_iter()
 8710                        .collect(),
 8711                    )),
 8712                    ..Default::default()
 8713                }),
 8714            ]))
 8715        },
 8716    );
 8717
 8718    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 8719        move |params, _| async move { Ok(params) }
 8720    });
 8721
 8722    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 8723    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 8724        let fake = fake_server.clone();
 8725        let lock = command_lock.clone();
 8726        move |params, _| {
 8727            assert_eq!(params.command, "the-command-for-code-action-1");
 8728            let fake = fake.clone();
 8729            let lock = lock.clone();
 8730            async move {
 8731                lock.lock().await;
 8732                fake.server
 8733                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 8734                        label: None,
 8735                        edit: lsp::WorkspaceEdit {
 8736                            changes: Some(
 8737                                [(
 8738                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 8739                                    vec![lsp::TextEdit {
 8740                                        range: lsp::Range::new(
 8741                                            lsp::Position::new(0, 0),
 8742                                            lsp::Position::new(0, 0),
 8743                                        ),
 8744                                        new_text: "applied-code-action-1-command\n".into(),
 8745                                    }],
 8746                                )]
 8747                                .into_iter()
 8748                                .collect(),
 8749                            ),
 8750                            ..Default::default()
 8751                        },
 8752                    })
 8753                    .await
 8754                    .unwrap();
 8755                Ok(Some(json!(null)))
 8756            }
 8757        }
 8758    });
 8759
 8760    cx.executor().start_waiting();
 8761    editor
 8762        .update_in(cx, |editor, window, cx| {
 8763            editor.perform_format(
 8764                project.clone(),
 8765                FormatTrigger::Manual,
 8766                FormatTarget::Buffers,
 8767                window,
 8768                cx,
 8769            )
 8770        })
 8771        .unwrap()
 8772        .await;
 8773    editor.update(cx, |editor, cx| {
 8774        assert_eq!(
 8775            editor.text(cx),
 8776            r#"
 8777                applied-code-action-2-edit
 8778                applied-code-action-1-command
 8779                applied-code-action-1-edit
 8780                applied-formatting
 8781                one
 8782                two
 8783                three
 8784            "#
 8785            .unindent()
 8786        );
 8787    });
 8788
 8789    editor.update_in(cx, |editor, window, cx| {
 8790        editor.undo(&Default::default(), window, cx);
 8791        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8792    });
 8793
 8794    // Perform a manual edit while waiting for an LSP command
 8795    // that's being run as part of a formatting code action.
 8796    let lock_guard = command_lock.lock().await;
 8797    let format = editor
 8798        .update_in(cx, |editor, window, cx| {
 8799            editor.perform_format(
 8800                project.clone(),
 8801                FormatTrigger::Manual,
 8802                FormatTarget::Buffers,
 8803                window,
 8804                cx,
 8805            )
 8806        })
 8807        .unwrap();
 8808    cx.run_until_parked();
 8809    editor.update(cx, |editor, cx| {
 8810        assert_eq!(
 8811            editor.text(cx),
 8812            r#"
 8813                applied-code-action-1-edit
 8814                applied-formatting
 8815                one
 8816                two
 8817                three
 8818            "#
 8819            .unindent()
 8820        );
 8821
 8822        editor.buffer.update(cx, |buffer, cx| {
 8823            let ix = buffer.len(cx);
 8824            buffer.edit([(ix..ix, "edited\n")], None, cx);
 8825        });
 8826    });
 8827
 8828    // Allow the LSP command to proceed. Because the buffer was edited,
 8829    // the second code action will not be run.
 8830    drop(lock_guard);
 8831    format.await;
 8832    editor.update_in(cx, |editor, window, cx| {
 8833        assert_eq!(
 8834            editor.text(cx),
 8835            r#"
 8836                applied-code-action-1-command
 8837                applied-code-action-1-edit
 8838                applied-formatting
 8839                one
 8840                two
 8841                three
 8842                edited
 8843            "#
 8844            .unindent()
 8845        );
 8846
 8847        // The manual edit is undone first, because it is the last thing the user did
 8848        // (even though the command completed afterwards).
 8849        editor.undo(&Default::default(), window, cx);
 8850        assert_eq!(
 8851            editor.text(cx),
 8852            r#"
 8853                applied-code-action-1-command
 8854                applied-code-action-1-edit
 8855                applied-formatting
 8856                one
 8857                two
 8858                three
 8859            "#
 8860            .unindent()
 8861        );
 8862
 8863        // All the formatting (including the command, which completed after the manual edit)
 8864        // is undone together.
 8865        editor.undo(&Default::default(), window, cx);
 8866        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8867    });
 8868}
 8869
 8870#[gpui::test]
 8871async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 8872    init_test(cx, |settings| {
 8873        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8874            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8875        ))
 8876    });
 8877
 8878    let fs = FakeFs::new(cx.executor());
 8879    fs.insert_file(path!("/file.ts"), Default::default()).await;
 8880
 8881    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8882
 8883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8884    language_registry.add(Arc::new(Language::new(
 8885        LanguageConfig {
 8886            name: "TypeScript".into(),
 8887            matcher: LanguageMatcher {
 8888                path_suffixes: vec!["ts".to_string()],
 8889                ..Default::default()
 8890            },
 8891            ..LanguageConfig::default()
 8892        },
 8893        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 8894    )));
 8895    update_test_language_settings(cx, |settings| {
 8896        settings.defaults.prettier = Some(PrettierSettings {
 8897            allowed: true,
 8898            ..PrettierSettings::default()
 8899        });
 8900    });
 8901    let mut fake_servers = language_registry.register_fake_lsp(
 8902        "TypeScript",
 8903        FakeLspAdapter {
 8904            capabilities: lsp::ServerCapabilities {
 8905                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8906                ..Default::default()
 8907            },
 8908            ..Default::default()
 8909        },
 8910    );
 8911
 8912    let buffer = project
 8913        .update(cx, |project, cx| {
 8914            project.open_local_buffer(path!("/file.ts"), cx)
 8915        })
 8916        .await
 8917        .unwrap();
 8918
 8919    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8920    let (editor, cx) = cx.add_window_view(|window, cx| {
 8921        build_editor_with_project(project.clone(), buffer, window, cx)
 8922    });
 8923    editor.update_in(cx, |editor, window, cx| {
 8924        editor.set_text(
 8925            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8926            window,
 8927            cx,
 8928        )
 8929    });
 8930
 8931    cx.executor().start_waiting();
 8932    let fake_server = fake_servers.next().await.unwrap();
 8933
 8934    let format = editor
 8935        .update_in(cx, |editor, window, cx| {
 8936            editor.perform_code_action_kind(
 8937                project.clone(),
 8938                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8939                window,
 8940                cx,
 8941            )
 8942        })
 8943        .unwrap();
 8944    fake_server
 8945        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 8946            assert_eq!(
 8947                params.text_document.uri,
 8948                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8949            );
 8950            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 8951                lsp::CodeAction {
 8952                    title: "Organize Imports".to_string(),
 8953                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 8954                    edit: Some(lsp::WorkspaceEdit {
 8955                        changes: Some(
 8956                            [(
 8957                                params.text_document.uri.clone(),
 8958                                vec![lsp::TextEdit::new(
 8959                                    lsp::Range::new(
 8960                                        lsp::Position::new(1, 0),
 8961                                        lsp::Position::new(2, 0),
 8962                                    ),
 8963                                    "".to_string(),
 8964                                )],
 8965                            )]
 8966                            .into_iter()
 8967                            .collect(),
 8968                        ),
 8969                        ..Default::default()
 8970                    }),
 8971                    ..Default::default()
 8972                },
 8973            )]))
 8974        })
 8975        .next()
 8976        .await;
 8977    cx.executor().start_waiting();
 8978    format.await;
 8979    assert_eq!(
 8980        editor.update(cx, |editor, cx| editor.text(cx)),
 8981        "import { a } from 'module';\n\nconst x = a;\n"
 8982    );
 8983
 8984    editor.update_in(cx, |editor, window, cx| {
 8985        editor.set_text(
 8986            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8987            window,
 8988            cx,
 8989        )
 8990    });
 8991    // Ensure we don't lock if code action hangs.
 8992    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8993        move |params, _| async move {
 8994            assert_eq!(
 8995                params.text_document.uri,
 8996                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8997            );
 8998            futures::future::pending::<()>().await;
 8999            unreachable!()
 9000        },
 9001    );
 9002    let format = editor
 9003        .update_in(cx, |editor, window, cx| {
 9004            editor.perform_code_action_kind(
 9005                project,
 9006                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9007                window,
 9008                cx,
 9009            )
 9010        })
 9011        .unwrap();
 9012    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9013    cx.executor().start_waiting();
 9014    format.await;
 9015    assert_eq!(
 9016        editor.update(cx, |editor, cx| editor.text(cx)),
 9017        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9018    );
 9019}
 9020
 9021#[gpui::test]
 9022async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9023    init_test(cx, |_| {});
 9024
 9025    let mut cx = EditorLspTestContext::new_rust(
 9026        lsp::ServerCapabilities {
 9027            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9028            ..Default::default()
 9029        },
 9030        cx,
 9031    )
 9032    .await;
 9033
 9034    cx.set_state(indoc! {"
 9035        one.twoˇ
 9036    "});
 9037
 9038    // The format request takes a long time. When it completes, it inserts
 9039    // a newline and an indent before the `.`
 9040    cx.lsp
 9041        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9042            let executor = cx.background_executor().clone();
 9043            async move {
 9044                executor.timer(Duration::from_millis(100)).await;
 9045                Ok(Some(vec![lsp::TextEdit {
 9046                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9047                    new_text: "\n    ".into(),
 9048                }]))
 9049            }
 9050        });
 9051
 9052    // Submit a format request.
 9053    let format_1 = cx
 9054        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9055        .unwrap();
 9056    cx.executor().run_until_parked();
 9057
 9058    // Submit a second format request.
 9059    let format_2 = cx
 9060        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9061        .unwrap();
 9062    cx.executor().run_until_parked();
 9063
 9064    // Wait for both format requests to complete
 9065    cx.executor().advance_clock(Duration::from_millis(200));
 9066    cx.executor().start_waiting();
 9067    format_1.await.unwrap();
 9068    cx.executor().start_waiting();
 9069    format_2.await.unwrap();
 9070
 9071    // The formatting edits only happens once.
 9072    cx.assert_editor_state(indoc! {"
 9073        one
 9074            .twoˇ
 9075    "});
 9076}
 9077
 9078#[gpui::test]
 9079async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9080    init_test(cx, |settings| {
 9081        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9082    });
 9083
 9084    let mut cx = EditorLspTestContext::new_rust(
 9085        lsp::ServerCapabilities {
 9086            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9087            ..Default::default()
 9088        },
 9089        cx,
 9090    )
 9091    .await;
 9092
 9093    // Set up a buffer white some trailing whitespace and no trailing newline.
 9094    cx.set_state(
 9095        &[
 9096            "one ",   //
 9097            "twoˇ",   //
 9098            "three ", //
 9099            "four",   //
 9100        ]
 9101        .join("\n"),
 9102    );
 9103
 9104    // Submit a format request.
 9105    let format = cx
 9106        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9107        .unwrap();
 9108
 9109    // Record which buffer changes have been sent to the language server
 9110    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9111    cx.lsp
 9112        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9113            let buffer_changes = buffer_changes.clone();
 9114            move |params, _| {
 9115                buffer_changes.lock().extend(
 9116                    params
 9117                        .content_changes
 9118                        .into_iter()
 9119                        .map(|e| (e.range.unwrap(), e.text)),
 9120                );
 9121            }
 9122        });
 9123
 9124    // Handle formatting requests to the language server.
 9125    cx.lsp
 9126        .set_request_handler::<lsp::request::Formatting, _, _>({
 9127            let buffer_changes = buffer_changes.clone();
 9128            move |_, _| {
 9129                // When formatting is requested, trailing whitespace has already been stripped,
 9130                // and the trailing newline has already been added.
 9131                assert_eq!(
 9132                    &buffer_changes.lock()[1..],
 9133                    &[
 9134                        (
 9135                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9136                            "".into()
 9137                        ),
 9138                        (
 9139                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9140                            "".into()
 9141                        ),
 9142                        (
 9143                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9144                            "\n".into()
 9145                        ),
 9146                    ]
 9147                );
 9148
 9149                // Insert blank lines between each line of the buffer.
 9150                async move {
 9151                    Ok(Some(vec![
 9152                        lsp::TextEdit {
 9153                            range: lsp::Range::new(
 9154                                lsp::Position::new(1, 0),
 9155                                lsp::Position::new(1, 0),
 9156                            ),
 9157                            new_text: "\n".into(),
 9158                        },
 9159                        lsp::TextEdit {
 9160                            range: lsp::Range::new(
 9161                                lsp::Position::new(2, 0),
 9162                                lsp::Position::new(2, 0),
 9163                            ),
 9164                            new_text: "\n".into(),
 9165                        },
 9166                    ]))
 9167                }
 9168            }
 9169        });
 9170
 9171    // After formatting the buffer, the trailing whitespace is stripped,
 9172    // a newline is appended, and the edits provided by the language server
 9173    // have been applied.
 9174    format.await.unwrap();
 9175    cx.assert_editor_state(
 9176        &[
 9177            "one",   //
 9178            "",      //
 9179            "twoˇ",  //
 9180            "",      //
 9181            "three", //
 9182            "four",  //
 9183            "",      //
 9184        ]
 9185        .join("\n"),
 9186    );
 9187
 9188    // Undoing the formatting undoes the trailing whitespace removal, the
 9189    // trailing newline, and the LSP edits.
 9190    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9191    cx.assert_editor_state(
 9192        &[
 9193            "one ",   //
 9194            "twoˇ",   //
 9195            "three ", //
 9196            "four",   //
 9197        ]
 9198        .join("\n"),
 9199    );
 9200}
 9201
 9202#[gpui::test]
 9203async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9204    cx: &mut TestAppContext,
 9205) {
 9206    init_test(cx, |_| {});
 9207
 9208    cx.update(|cx| {
 9209        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9210            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9211                settings.auto_signature_help = Some(true);
 9212            });
 9213        });
 9214    });
 9215
 9216    let mut cx = EditorLspTestContext::new_rust(
 9217        lsp::ServerCapabilities {
 9218            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9219                ..Default::default()
 9220            }),
 9221            ..Default::default()
 9222        },
 9223        cx,
 9224    )
 9225    .await;
 9226
 9227    let language = Language::new(
 9228        LanguageConfig {
 9229            name: "Rust".into(),
 9230            brackets: BracketPairConfig {
 9231                pairs: vec![
 9232                    BracketPair {
 9233                        start: "{".to_string(),
 9234                        end: "}".to_string(),
 9235                        close: true,
 9236                        surround: true,
 9237                        newline: true,
 9238                    },
 9239                    BracketPair {
 9240                        start: "(".to_string(),
 9241                        end: ")".to_string(),
 9242                        close: true,
 9243                        surround: true,
 9244                        newline: true,
 9245                    },
 9246                    BracketPair {
 9247                        start: "/*".to_string(),
 9248                        end: " */".to_string(),
 9249                        close: true,
 9250                        surround: true,
 9251                        newline: true,
 9252                    },
 9253                    BracketPair {
 9254                        start: "[".to_string(),
 9255                        end: "]".to_string(),
 9256                        close: false,
 9257                        surround: false,
 9258                        newline: true,
 9259                    },
 9260                    BracketPair {
 9261                        start: "\"".to_string(),
 9262                        end: "\"".to_string(),
 9263                        close: true,
 9264                        surround: true,
 9265                        newline: false,
 9266                    },
 9267                    BracketPair {
 9268                        start: "<".to_string(),
 9269                        end: ">".to_string(),
 9270                        close: false,
 9271                        surround: true,
 9272                        newline: true,
 9273                    },
 9274                ],
 9275                ..Default::default()
 9276            },
 9277            autoclose_before: "})]".to_string(),
 9278            ..Default::default()
 9279        },
 9280        Some(tree_sitter_rust::LANGUAGE.into()),
 9281    );
 9282    let language = Arc::new(language);
 9283
 9284    cx.language_registry().add(language.clone());
 9285    cx.update_buffer(|buffer, cx| {
 9286        buffer.set_language(Some(language), cx);
 9287    });
 9288
 9289    cx.set_state(
 9290        &r#"
 9291            fn main() {
 9292                sampleˇ
 9293            }
 9294        "#
 9295        .unindent(),
 9296    );
 9297
 9298    cx.update_editor(|editor, window, cx| {
 9299        editor.handle_input("(", window, cx);
 9300    });
 9301    cx.assert_editor_state(
 9302        &"
 9303            fn main() {
 9304                sample(ˇ)
 9305            }
 9306        "
 9307        .unindent(),
 9308    );
 9309
 9310    let mocked_response = lsp::SignatureHelp {
 9311        signatures: vec![lsp::SignatureInformation {
 9312            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9313            documentation: None,
 9314            parameters: Some(vec![
 9315                lsp::ParameterInformation {
 9316                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9317                    documentation: None,
 9318                },
 9319                lsp::ParameterInformation {
 9320                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9321                    documentation: None,
 9322                },
 9323            ]),
 9324            active_parameter: None,
 9325        }],
 9326        active_signature: Some(0),
 9327        active_parameter: Some(0),
 9328    };
 9329    handle_signature_help_request(&mut cx, mocked_response).await;
 9330
 9331    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9332        .await;
 9333
 9334    cx.editor(|editor, _, _| {
 9335        let signature_help_state = editor.signature_help_state.popover().cloned();
 9336        assert_eq!(
 9337            signature_help_state.unwrap().label,
 9338            "param1: u8, param2: u8"
 9339        );
 9340    });
 9341}
 9342
 9343#[gpui::test]
 9344async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9345    init_test(cx, |_| {});
 9346
 9347    cx.update(|cx| {
 9348        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9349            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9350                settings.auto_signature_help = Some(false);
 9351                settings.show_signature_help_after_edits = Some(false);
 9352            });
 9353        });
 9354    });
 9355
 9356    let mut cx = EditorLspTestContext::new_rust(
 9357        lsp::ServerCapabilities {
 9358            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9359                ..Default::default()
 9360            }),
 9361            ..Default::default()
 9362        },
 9363        cx,
 9364    )
 9365    .await;
 9366
 9367    let language = Language::new(
 9368        LanguageConfig {
 9369            name: "Rust".into(),
 9370            brackets: BracketPairConfig {
 9371                pairs: vec![
 9372                    BracketPair {
 9373                        start: "{".to_string(),
 9374                        end: "}".to_string(),
 9375                        close: true,
 9376                        surround: true,
 9377                        newline: true,
 9378                    },
 9379                    BracketPair {
 9380                        start: "(".to_string(),
 9381                        end: ")".to_string(),
 9382                        close: true,
 9383                        surround: true,
 9384                        newline: true,
 9385                    },
 9386                    BracketPair {
 9387                        start: "/*".to_string(),
 9388                        end: " */".to_string(),
 9389                        close: true,
 9390                        surround: true,
 9391                        newline: true,
 9392                    },
 9393                    BracketPair {
 9394                        start: "[".to_string(),
 9395                        end: "]".to_string(),
 9396                        close: false,
 9397                        surround: false,
 9398                        newline: true,
 9399                    },
 9400                    BracketPair {
 9401                        start: "\"".to_string(),
 9402                        end: "\"".to_string(),
 9403                        close: true,
 9404                        surround: true,
 9405                        newline: false,
 9406                    },
 9407                    BracketPair {
 9408                        start: "<".to_string(),
 9409                        end: ">".to_string(),
 9410                        close: false,
 9411                        surround: true,
 9412                        newline: true,
 9413                    },
 9414                ],
 9415                ..Default::default()
 9416            },
 9417            autoclose_before: "})]".to_string(),
 9418            ..Default::default()
 9419        },
 9420        Some(tree_sitter_rust::LANGUAGE.into()),
 9421    );
 9422    let language = Arc::new(language);
 9423
 9424    cx.language_registry().add(language.clone());
 9425    cx.update_buffer(|buffer, cx| {
 9426        buffer.set_language(Some(language), cx);
 9427    });
 9428
 9429    // Ensure that signature_help is not called when no signature help is enabled.
 9430    cx.set_state(
 9431        &r#"
 9432            fn main() {
 9433                sampleˇ
 9434            }
 9435        "#
 9436        .unindent(),
 9437    );
 9438    cx.update_editor(|editor, window, cx| {
 9439        editor.handle_input("(", window, cx);
 9440    });
 9441    cx.assert_editor_state(
 9442        &"
 9443            fn main() {
 9444                sample(ˇ)
 9445            }
 9446        "
 9447        .unindent(),
 9448    );
 9449    cx.editor(|editor, _, _| {
 9450        assert!(editor.signature_help_state.task().is_none());
 9451    });
 9452
 9453    let mocked_response = lsp::SignatureHelp {
 9454        signatures: vec![lsp::SignatureInformation {
 9455            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9456            documentation: None,
 9457            parameters: Some(vec![
 9458                lsp::ParameterInformation {
 9459                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9460                    documentation: None,
 9461                },
 9462                lsp::ParameterInformation {
 9463                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9464                    documentation: None,
 9465                },
 9466            ]),
 9467            active_parameter: None,
 9468        }],
 9469        active_signature: Some(0),
 9470        active_parameter: Some(0),
 9471    };
 9472
 9473    // Ensure that signature_help is called when enabled afte edits
 9474    cx.update(|_, cx| {
 9475        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9476            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9477                settings.auto_signature_help = Some(false);
 9478                settings.show_signature_help_after_edits = Some(true);
 9479            });
 9480        });
 9481    });
 9482    cx.set_state(
 9483        &r#"
 9484            fn main() {
 9485                sampleˇ
 9486            }
 9487        "#
 9488        .unindent(),
 9489    );
 9490    cx.update_editor(|editor, window, cx| {
 9491        editor.handle_input("(", window, cx);
 9492    });
 9493    cx.assert_editor_state(
 9494        &"
 9495            fn main() {
 9496                sample(ˇ)
 9497            }
 9498        "
 9499        .unindent(),
 9500    );
 9501    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9502    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9503        .await;
 9504    cx.update_editor(|editor, _, _| {
 9505        let signature_help_state = editor.signature_help_state.popover().cloned();
 9506        assert!(signature_help_state.is_some());
 9507        assert_eq!(
 9508            signature_help_state.unwrap().label,
 9509            "param1: u8, param2: u8"
 9510        );
 9511        editor.signature_help_state = SignatureHelpState::default();
 9512    });
 9513
 9514    // Ensure that signature_help is called when auto signature help override is enabled
 9515    cx.update(|_, cx| {
 9516        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9517            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9518                settings.auto_signature_help = Some(true);
 9519                settings.show_signature_help_after_edits = Some(false);
 9520            });
 9521        });
 9522    });
 9523    cx.set_state(
 9524        &r#"
 9525            fn main() {
 9526                sampleˇ
 9527            }
 9528        "#
 9529        .unindent(),
 9530    );
 9531    cx.update_editor(|editor, window, cx| {
 9532        editor.handle_input("(", window, cx);
 9533    });
 9534    cx.assert_editor_state(
 9535        &"
 9536            fn main() {
 9537                sample(ˇ)
 9538            }
 9539        "
 9540        .unindent(),
 9541    );
 9542    handle_signature_help_request(&mut cx, mocked_response).await;
 9543    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9544        .await;
 9545    cx.editor(|editor, _, _| {
 9546        let signature_help_state = editor.signature_help_state.popover().cloned();
 9547        assert!(signature_help_state.is_some());
 9548        assert_eq!(
 9549            signature_help_state.unwrap().label,
 9550            "param1: u8, param2: u8"
 9551        );
 9552    });
 9553}
 9554
 9555#[gpui::test]
 9556async fn test_signature_help(cx: &mut TestAppContext) {
 9557    init_test(cx, |_| {});
 9558    cx.update(|cx| {
 9559        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9560            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9561                settings.auto_signature_help = Some(true);
 9562            });
 9563        });
 9564    });
 9565
 9566    let mut cx = EditorLspTestContext::new_rust(
 9567        lsp::ServerCapabilities {
 9568            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9569                ..Default::default()
 9570            }),
 9571            ..Default::default()
 9572        },
 9573        cx,
 9574    )
 9575    .await;
 9576
 9577    // A test that directly calls `show_signature_help`
 9578    cx.update_editor(|editor, window, cx| {
 9579        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9580    });
 9581
 9582    let mocked_response = lsp::SignatureHelp {
 9583        signatures: vec![lsp::SignatureInformation {
 9584            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9585            documentation: None,
 9586            parameters: Some(vec![
 9587                lsp::ParameterInformation {
 9588                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9589                    documentation: None,
 9590                },
 9591                lsp::ParameterInformation {
 9592                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9593                    documentation: None,
 9594                },
 9595            ]),
 9596            active_parameter: None,
 9597        }],
 9598        active_signature: Some(0),
 9599        active_parameter: Some(0),
 9600    };
 9601    handle_signature_help_request(&mut cx, mocked_response).await;
 9602
 9603    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9604        .await;
 9605
 9606    cx.editor(|editor, _, _| {
 9607        let signature_help_state = editor.signature_help_state.popover().cloned();
 9608        assert!(signature_help_state.is_some());
 9609        assert_eq!(
 9610            signature_help_state.unwrap().label,
 9611            "param1: u8, param2: u8"
 9612        );
 9613    });
 9614
 9615    // When exiting outside from inside the brackets, `signature_help` is closed.
 9616    cx.set_state(indoc! {"
 9617        fn main() {
 9618            sample(ˇ);
 9619        }
 9620
 9621        fn sample(param1: u8, param2: u8) {}
 9622    "});
 9623
 9624    cx.update_editor(|editor, window, cx| {
 9625        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
 9626    });
 9627
 9628    let mocked_response = lsp::SignatureHelp {
 9629        signatures: Vec::new(),
 9630        active_signature: None,
 9631        active_parameter: None,
 9632    };
 9633    handle_signature_help_request(&mut cx, mocked_response).await;
 9634
 9635    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9636        .await;
 9637
 9638    cx.editor(|editor, _, _| {
 9639        assert!(!editor.signature_help_state.is_shown());
 9640    });
 9641
 9642    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
 9643    cx.set_state(indoc! {"
 9644        fn main() {
 9645            sample(ˇ);
 9646        }
 9647
 9648        fn sample(param1: u8, param2: u8) {}
 9649    "});
 9650
 9651    let mocked_response = lsp::SignatureHelp {
 9652        signatures: vec![lsp::SignatureInformation {
 9653            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9654            documentation: None,
 9655            parameters: Some(vec![
 9656                lsp::ParameterInformation {
 9657                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9658                    documentation: None,
 9659                },
 9660                lsp::ParameterInformation {
 9661                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9662                    documentation: None,
 9663                },
 9664            ]),
 9665            active_parameter: None,
 9666        }],
 9667        active_signature: Some(0),
 9668        active_parameter: Some(0),
 9669    };
 9670    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9671    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9672        .await;
 9673    cx.editor(|editor, _, _| {
 9674        assert!(editor.signature_help_state.is_shown());
 9675    });
 9676
 9677    // Restore the popover with more parameter input
 9678    cx.set_state(indoc! {"
 9679        fn main() {
 9680            sample(param1, param2ˇ);
 9681        }
 9682
 9683        fn sample(param1: u8, param2: u8) {}
 9684    "});
 9685
 9686    let mocked_response = lsp::SignatureHelp {
 9687        signatures: vec![lsp::SignatureInformation {
 9688            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9689            documentation: None,
 9690            parameters: Some(vec![
 9691                lsp::ParameterInformation {
 9692                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9693                    documentation: None,
 9694                },
 9695                lsp::ParameterInformation {
 9696                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9697                    documentation: None,
 9698                },
 9699            ]),
 9700            active_parameter: None,
 9701        }],
 9702        active_signature: Some(0),
 9703        active_parameter: Some(1),
 9704    };
 9705    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9706    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9707        .await;
 9708
 9709    // When selecting a range, the popover is gone.
 9710    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
 9711    cx.update_editor(|editor, window, cx| {
 9712        editor.change_selections(None, window, cx, |s| {
 9713            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9714        })
 9715    });
 9716    cx.assert_editor_state(indoc! {"
 9717        fn main() {
 9718            sample(param1, «ˇparam2»);
 9719        }
 9720
 9721        fn sample(param1: u8, param2: u8) {}
 9722    "});
 9723    cx.editor(|editor, _, _| {
 9724        assert!(!editor.signature_help_state.is_shown());
 9725    });
 9726
 9727    // When unselecting again, the popover is back if within the brackets.
 9728    cx.update_editor(|editor, window, cx| {
 9729        editor.change_selections(None, window, cx, |s| {
 9730            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9731        })
 9732    });
 9733    cx.assert_editor_state(indoc! {"
 9734        fn main() {
 9735            sample(param1, ˇparam2);
 9736        }
 9737
 9738        fn sample(param1: u8, param2: u8) {}
 9739    "});
 9740    handle_signature_help_request(&mut cx, mocked_response).await;
 9741    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9742        .await;
 9743    cx.editor(|editor, _, _| {
 9744        assert!(editor.signature_help_state.is_shown());
 9745    });
 9746
 9747    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
 9748    cx.update_editor(|editor, window, cx| {
 9749        editor.change_selections(None, window, cx, |s| {
 9750            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
 9751            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9752        })
 9753    });
 9754    cx.assert_editor_state(indoc! {"
 9755        fn main() {
 9756            sample(param1, ˇparam2);
 9757        }
 9758
 9759        fn sample(param1: u8, param2: u8) {}
 9760    "});
 9761
 9762    let mocked_response = lsp::SignatureHelp {
 9763        signatures: vec![lsp::SignatureInformation {
 9764            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9765            documentation: None,
 9766            parameters: Some(vec![
 9767                lsp::ParameterInformation {
 9768                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9769                    documentation: None,
 9770                },
 9771                lsp::ParameterInformation {
 9772                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9773                    documentation: None,
 9774                },
 9775            ]),
 9776            active_parameter: None,
 9777        }],
 9778        active_signature: Some(0),
 9779        active_parameter: Some(1),
 9780    };
 9781    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9782    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9783        .await;
 9784    cx.update_editor(|editor, _, cx| {
 9785        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
 9786    });
 9787    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9788        .await;
 9789    cx.update_editor(|editor, window, cx| {
 9790        editor.change_selections(None, window, cx, |s| {
 9791            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9792        })
 9793    });
 9794    cx.assert_editor_state(indoc! {"
 9795        fn main() {
 9796            sample(param1, «ˇparam2»);
 9797        }
 9798
 9799        fn sample(param1: u8, param2: u8) {}
 9800    "});
 9801    cx.update_editor(|editor, window, cx| {
 9802        editor.change_selections(None, window, cx, |s| {
 9803            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9804        })
 9805    });
 9806    cx.assert_editor_state(indoc! {"
 9807        fn main() {
 9808            sample(param1, ˇparam2);
 9809        }
 9810
 9811        fn sample(param1: u8, param2: u8) {}
 9812    "});
 9813    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
 9814        .await;
 9815}
 9816
 9817#[gpui::test]
 9818async fn test_completion_mode(cx: &mut TestAppContext) {
 9819    init_test(cx, |_| {});
 9820    let mut cx = EditorLspTestContext::new_rust(
 9821        lsp::ServerCapabilities {
 9822            completion_provider: Some(lsp::CompletionOptions {
 9823                resolve_provider: Some(true),
 9824                ..Default::default()
 9825            }),
 9826            ..Default::default()
 9827        },
 9828        cx,
 9829    )
 9830    .await;
 9831
 9832    struct Run {
 9833        run_description: &'static str,
 9834        initial_state: String,
 9835        buffer_marked_text: String,
 9836        completion_text: &'static str,
 9837        expected_with_insert_mode: String,
 9838        expected_with_replace_mode: String,
 9839        expected_with_replace_subsequence_mode: String,
 9840        expected_with_replace_suffix_mode: String,
 9841    }
 9842
 9843    let runs = [
 9844        Run {
 9845            run_description: "Start of word matches completion text",
 9846            initial_state: "before ediˇ after".into(),
 9847            buffer_marked_text: "before <edi|> after".into(),
 9848            completion_text: "editor",
 9849            expected_with_insert_mode: "before editorˇ after".into(),
 9850            expected_with_replace_mode: "before editorˇ after".into(),
 9851            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9852            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9853        },
 9854        Run {
 9855            run_description: "Accept same text at the middle of the word",
 9856            initial_state: "before ediˇtor after".into(),
 9857            buffer_marked_text: "before <edi|tor> after".into(),
 9858            completion_text: "editor",
 9859            expected_with_insert_mode: "before editorˇtor after".into(),
 9860            expected_with_replace_mode: "before editorˇ after".into(),
 9861            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9862            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9863        },
 9864        Run {
 9865            run_description: "End of word matches completion text -- cursor at end",
 9866            initial_state: "before torˇ after".into(),
 9867            buffer_marked_text: "before <tor|> after".into(),
 9868            completion_text: "editor",
 9869            expected_with_insert_mode: "before editorˇ after".into(),
 9870            expected_with_replace_mode: "before editorˇ after".into(),
 9871            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9872            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9873        },
 9874        Run {
 9875            run_description: "End of word matches completion text -- cursor at start",
 9876            initial_state: "before ˇtor after".into(),
 9877            buffer_marked_text: "before <|tor> after".into(),
 9878            completion_text: "editor",
 9879            expected_with_insert_mode: "before editorˇtor after".into(),
 9880            expected_with_replace_mode: "before editorˇ after".into(),
 9881            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9882            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9883        },
 9884        Run {
 9885            run_description: "Prepend text containing whitespace",
 9886            initial_state: "pˇfield: bool".into(),
 9887            buffer_marked_text: "<p|field>: bool".into(),
 9888            completion_text: "pub ",
 9889            expected_with_insert_mode: "pub ˇfield: bool".into(),
 9890            expected_with_replace_mode: "pub ˇ: bool".into(),
 9891            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
 9892            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
 9893        },
 9894        Run {
 9895            run_description: "Add element to start of list",
 9896            initial_state: "[element_ˇelement_2]".into(),
 9897            buffer_marked_text: "[<element_|element_2>]".into(),
 9898            completion_text: "element_1",
 9899            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
 9900            expected_with_replace_mode: "[element_1ˇ]".into(),
 9901            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
 9902            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
 9903        },
 9904        Run {
 9905            run_description: "Add element to start of list -- first and second elements are equal",
 9906            initial_state: "[elˇelement]".into(),
 9907            buffer_marked_text: "[<el|element>]".into(),
 9908            completion_text: "element",
 9909            expected_with_insert_mode: "[elementˇelement]".into(),
 9910            expected_with_replace_mode: "[elementˇ]".into(),
 9911            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
 9912            expected_with_replace_suffix_mode: "[elementˇ]".into(),
 9913        },
 9914        Run {
 9915            run_description: "Ends with matching suffix",
 9916            initial_state: "SubˇError".into(),
 9917            buffer_marked_text: "<Sub|Error>".into(),
 9918            completion_text: "SubscriptionError",
 9919            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
 9920            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9921            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9922            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
 9923        },
 9924        Run {
 9925            run_description: "Suffix is a subsequence -- contiguous",
 9926            initial_state: "SubˇErr".into(),
 9927            buffer_marked_text: "<Sub|Err>".into(),
 9928            completion_text: "SubscriptionError",
 9929            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
 9930            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9931            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9932            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
 9933        },
 9934        Run {
 9935            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
 9936            initial_state: "Suˇscrirr".into(),
 9937            buffer_marked_text: "<Su|scrirr>".into(),
 9938            completion_text: "SubscriptionError",
 9939            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
 9940            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9941            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9942            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
 9943        },
 9944        Run {
 9945            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
 9946            initial_state: "foo(indˇix)".into(),
 9947            buffer_marked_text: "foo(<ind|ix>)".into(),
 9948            completion_text: "node_index",
 9949            expected_with_insert_mode: "foo(node_indexˇix)".into(),
 9950            expected_with_replace_mode: "foo(node_indexˇ)".into(),
 9951            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
 9952            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
 9953        },
 9954    ];
 9955
 9956    for run in runs {
 9957        let run_variations = [
 9958            (LspInsertMode::Insert, run.expected_with_insert_mode),
 9959            (LspInsertMode::Replace, run.expected_with_replace_mode),
 9960            (
 9961                LspInsertMode::ReplaceSubsequence,
 9962                run.expected_with_replace_subsequence_mode,
 9963            ),
 9964            (
 9965                LspInsertMode::ReplaceSuffix,
 9966                run.expected_with_replace_suffix_mode,
 9967            ),
 9968        ];
 9969
 9970        for (lsp_insert_mode, expected_text) in run_variations {
 9971            eprintln!(
 9972                "run = {:?}, mode = {lsp_insert_mode:.?}",
 9973                run.run_description,
 9974            );
 9975
 9976            update_test_language_settings(&mut cx, |settings| {
 9977                settings.defaults.completions = Some(CompletionSettings {
 9978                    lsp_insert_mode,
 9979                    words: WordsCompletionMode::Disabled,
 9980                    lsp: true,
 9981                    lsp_fetch_timeout_ms: 0,
 9982                });
 9983            });
 9984
 9985            cx.set_state(&run.initial_state);
 9986            cx.update_editor(|editor, window, cx| {
 9987                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9988            });
 9989
 9990            let counter = Arc::new(AtomicUsize::new(0));
 9991            handle_completion_request_with_insert_and_replace(
 9992                &mut cx,
 9993                &run.buffer_marked_text,
 9994                vec![run.completion_text],
 9995                counter.clone(),
 9996            )
 9997            .await;
 9998            cx.condition(|editor, _| editor.context_menu_visible())
 9999                .await;
10000            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10001
10002            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10003                editor
10004                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10005                    .unwrap()
10006            });
10007            cx.assert_editor_state(&expected_text);
10008            handle_resolve_completion_request(&mut cx, None).await;
10009            apply_additional_edits.await.unwrap();
10010        }
10011    }
10012}
10013
10014#[gpui::test]
10015async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10016    init_test(cx, |_| {});
10017    let mut cx = EditorLspTestContext::new_rust(
10018        lsp::ServerCapabilities {
10019            completion_provider: Some(lsp::CompletionOptions {
10020                resolve_provider: Some(true),
10021                ..Default::default()
10022            }),
10023            ..Default::default()
10024        },
10025        cx,
10026    )
10027    .await;
10028
10029    let initial_state = "SubˇError";
10030    let buffer_marked_text = "<Sub|Error>";
10031    let completion_text = "SubscriptionError";
10032    let expected_with_insert_mode = "SubscriptionErrorˇError";
10033    let expected_with_replace_mode = "SubscriptionErrorˇ";
10034
10035    update_test_language_settings(&mut cx, |settings| {
10036        settings.defaults.completions = Some(CompletionSettings {
10037            words: WordsCompletionMode::Disabled,
10038            // set the opposite here to ensure that the action is overriding the default behavior
10039            lsp_insert_mode: LspInsertMode::Insert,
10040            lsp: true,
10041            lsp_fetch_timeout_ms: 0,
10042        });
10043    });
10044
10045    cx.set_state(initial_state);
10046    cx.update_editor(|editor, window, cx| {
10047        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10048    });
10049
10050    let counter = Arc::new(AtomicUsize::new(0));
10051    handle_completion_request_with_insert_and_replace(
10052        &mut cx,
10053        &buffer_marked_text,
10054        vec![completion_text],
10055        counter.clone(),
10056    )
10057    .await;
10058    cx.condition(|editor, _| editor.context_menu_visible())
10059        .await;
10060    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10061
10062    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10063        editor
10064            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10065            .unwrap()
10066    });
10067    cx.assert_editor_state(&expected_with_replace_mode);
10068    handle_resolve_completion_request(&mut cx, None).await;
10069    apply_additional_edits.await.unwrap();
10070
10071    update_test_language_settings(&mut cx, |settings| {
10072        settings.defaults.completions = Some(CompletionSettings {
10073            words: WordsCompletionMode::Disabled,
10074            // set the opposite here to ensure that the action is overriding the default behavior
10075            lsp_insert_mode: LspInsertMode::Replace,
10076            lsp: true,
10077            lsp_fetch_timeout_ms: 0,
10078        });
10079    });
10080
10081    cx.set_state(initial_state);
10082    cx.update_editor(|editor, window, cx| {
10083        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10084    });
10085    handle_completion_request_with_insert_and_replace(
10086        &mut cx,
10087        &buffer_marked_text,
10088        vec![completion_text],
10089        counter.clone(),
10090    )
10091    .await;
10092    cx.condition(|editor, _| editor.context_menu_visible())
10093        .await;
10094    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10095
10096    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10097        editor
10098            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10099            .unwrap()
10100    });
10101    cx.assert_editor_state(&expected_with_insert_mode);
10102    handle_resolve_completion_request(&mut cx, None).await;
10103    apply_additional_edits.await.unwrap();
10104}
10105
10106#[gpui::test]
10107async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10108    init_test(cx, |_| {});
10109    let mut cx = EditorLspTestContext::new_rust(
10110        lsp::ServerCapabilities {
10111            completion_provider: Some(lsp::CompletionOptions {
10112                resolve_provider: Some(true),
10113                ..Default::default()
10114            }),
10115            ..Default::default()
10116        },
10117        cx,
10118    )
10119    .await;
10120
10121    // scenario: surrounding text matches completion text
10122    let completion_text = "to_offset";
10123    let initial_state = indoc! {"
10124        1. buf.to_offˇsuffix
10125        2. buf.to_offˇsuf
10126        3. buf.to_offˇfix
10127        4. buf.to_offˇ
10128        5. into_offˇensive
10129        6. ˇsuffix
10130        7. let ˇ //
10131        8. aaˇzz
10132        9. buf.to_off«zzzzzˇ»suffix
10133        10. buf.«ˇzzzzz»suffix
10134        11. to_off«ˇzzzzz»
10135
10136        buf.to_offˇsuffix  // newest cursor
10137    "};
10138    let completion_marked_buffer = indoc! {"
10139        1. buf.to_offsuffix
10140        2. buf.to_offsuf
10141        3. buf.to_offfix
10142        4. buf.to_off
10143        5. into_offensive
10144        6. suffix
10145        7. let  //
10146        8. aazz
10147        9. buf.to_offzzzzzsuffix
10148        10. buf.zzzzzsuffix
10149        11. to_offzzzzz
10150
10151        buf.<to_off|suffix>  // newest cursor
10152    "};
10153    let expected = indoc! {"
10154        1. buf.to_offsetˇ
10155        2. buf.to_offsetˇsuf
10156        3. buf.to_offsetˇfix
10157        4. buf.to_offsetˇ
10158        5. into_offsetˇensive
10159        6. to_offsetˇsuffix
10160        7. let to_offsetˇ //
10161        8. aato_offsetˇzz
10162        9. buf.to_offsetˇ
10163        10. buf.to_offsetˇsuffix
10164        11. to_offsetˇ
10165
10166        buf.to_offsetˇ  // newest cursor
10167    "};
10168    cx.set_state(initial_state);
10169    cx.update_editor(|editor, window, cx| {
10170        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10171    });
10172    handle_completion_request_with_insert_and_replace(
10173        &mut cx,
10174        completion_marked_buffer,
10175        vec![completion_text],
10176        Arc::new(AtomicUsize::new(0)),
10177    )
10178    .await;
10179    cx.condition(|editor, _| editor.context_menu_visible())
10180        .await;
10181    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10182        editor
10183            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10184            .unwrap()
10185    });
10186    cx.assert_editor_state(expected);
10187    handle_resolve_completion_request(&mut cx, None).await;
10188    apply_additional_edits.await.unwrap();
10189
10190    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10191    let completion_text = "foo_and_bar";
10192    let initial_state = indoc! {"
10193        1. ooanbˇ
10194        2. zooanbˇ
10195        3. ooanbˇz
10196        4. zooanbˇz
10197        5. ooanˇ
10198        6. oanbˇ
10199
10200        ooanbˇ
10201    "};
10202    let completion_marked_buffer = indoc! {"
10203        1. ooanb
10204        2. zooanb
10205        3. ooanbz
10206        4. zooanbz
10207        5. ooan
10208        6. oanb
10209
10210        <ooanb|>
10211    "};
10212    let expected = indoc! {"
10213        1. foo_and_barˇ
10214        2. zfoo_and_barˇ
10215        3. foo_and_barˇz
10216        4. zfoo_and_barˇz
10217        5. ooanfoo_and_barˇ
10218        6. oanbfoo_and_barˇ
10219
10220        foo_and_barˇ
10221    "};
10222    cx.set_state(initial_state);
10223    cx.update_editor(|editor, window, cx| {
10224        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10225    });
10226    handle_completion_request_with_insert_and_replace(
10227        &mut cx,
10228        completion_marked_buffer,
10229        vec![completion_text],
10230        Arc::new(AtomicUsize::new(0)),
10231    )
10232    .await;
10233    cx.condition(|editor, _| editor.context_menu_visible())
10234        .await;
10235    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10236        editor
10237            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10238            .unwrap()
10239    });
10240    cx.assert_editor_state(expected);
10241    handle_resolve_completion_request(&mut cx, None).await;
10242    apply_additional_edits.await.unwrap();
10243
10244    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10245    // (expects the same as if it was inserted at the end)
10246    let completion_text = "foo_and_bar";
10247    let initial_state = indoc! {"
10248        1. ooˇanb
10249        2. zooˇanb
10250        3. ooˇanbz
10251        4. zooˇanbz
10252
10253        ooˇanb
10254    "};
10255    let completion_marked_buffer = indoc! {"
10256        1. ooanb
10257        2. zooanb
10258        3. ooanbz
10259        4. zooanbz
10260
10261        <oo|anb>
10262    "};
10263    let expected = indoc! {"
10264        1. foo_and_barˇ
10265        2. zfoo_and_barˇ
10266        3. foo_and_barˇz
10267        4. zfoo_and_barˇz
10268
10269        foo_and_barˇ
10270    "};
10271    cx.set_state(initial_state);
10272    cx.update_editor(|editor, window, cx| {
10273        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10274    });
10275    handle_completion_request_with_insert_and_replace(
10276        &mut cx,
10277        completion_marked_buffer,
10278        vec![completion_text],
10279        Arc::new(AtomicUsize::new(0)),
10280    )
10281    .await;
10282    cx.condition(|editor, _| editor.context_menu_visible())
10283        .await;
10284    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10285        editor
10286            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10287            .unwrap()
10288    });
10289    cx.assert_editor_state(expected);
10290    handle_resolve_completion_request(&mut cx, None).await;
10291    apply_additional_edits.await.unwrap();
10292}
10293
10294// This used to crash
10295#[gpui::test]
10296async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10297    init_test(cx, |_| {});
10298
10299    let buffer_text = indoc! {"
10300        fn main() {
10301            10.satu;
10302
10303            //
10304            // separate cursors so they open in different excerpts (manually reproducible)
10305            //
10306
10307            10.satu20;
10308        }
10309    "};
10310    let multibuffer_text_with_selections = indoc! {"
10311        fn main() {
10312            10.satuˇ;
10313
10314            //
10315
10316            //
10317
10318            10.satuˇ20;
10319        }
10320    "};
10321    let expected_multibuffer = indoc! {"
10322        fn main() {
10323            10.saturating_sub()ˇ;
10324
10325            //
10326
10327            //
10328
10329            10.saturating_sub()ˇ;
10330        }
10331    "};
10332
10333    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10334    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10335
10336    let fs = FakeFs::new(cx.executor());
10337    fs.insert_tree(
10338        path!("/a"),
10339        json!({
10340            "main.rs": buffer_text,
10341        }),
10342    )
10343    .await;
10344
10345    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10346    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10347    language_registry.add(rust_lang());
10348    let mut fake_servers = language_registry.register_fake_lsp(
10349        "Rust",
10350        FakeLspAdapter {
10351            capabilities: lsp::ServerCapabilities {
10352                completion_provider: Some(lsp::CompletionOptions {
10353                    resolve_provider: None,
10354                    ..lsp::CompletionOptions::default()
10355                }),
10356                ..lsp::ServerCapabilities::default()
10357            },
10358            ..FakeLspAdapter::default()
10359        },
10360    );
10361    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10362    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10363    let buffer = project
10364        .update(cx, |project, cx| {
10365            project.open_local_buffer(path!("/a/main.rs"), cx)
10366        })
10367        .await
10368        .unwrap();
10369
10370    let multi_buffer = cx.new(|cx| {
10371        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10372        multi_buffer.push_excerpts(
10373            buffer.clone(),
10374            [ExcerptRange::new(0..first_excerpt_end)],
10375            cx,
10376        );
10377        multi_buffer.push_excerpts(
10378            buffer.clone(),
10379            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10380            cx,
10381        );
10382        multi_buffer
10383    });
10384
10385    let editor = workspace
10386        .update(cx, |_, window, cx| {
10387            cx.new(|cx| {
10388                Editor::new(
10389                    EditorMode::Full {
10390                        scale_ui_elements_with_buffer_font_size: false,
10391                        show_active_line_background: false,
10392                    },
10393                    multi_buffer.clone(),
10394                    Some(project.clone()),
10395                    window,
10396                    cx,
10397                )
10398            })
10399        })
10400        .unwrap();
10401
10402    let pane = workspace
10403        .update(cx, |workspace, _, _| workspace.active_pane().clone())
10404        .unwrap();
10405    pane.update_in(cx, |pane, window, cx| {
10406        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10407    });
10408
10409    let fake_server = fake_servers.next().await.unwrap();
10410
10411    editor.update_in(cx, |editor, window, cx| {
10412        editor.change_selections(None, window, cx, |s| {
10413            s.select_ranges([
10414                Point::new(1, 11)..Point::new(1, 11),
10415                Point::new(7, 11)..Point::new(7, 11),
10416            ])
10417        });
10418
10419        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10420    });
10421
10422    editor.update_in(cx, |editor, window, cx| {
10423        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10424    });
10425
10426    fake_server
10427        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10428            let completion_item = lsp::CompletionItem {
10429                label: "saturating_sub()".into(),
10430                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10431                    lsp::InsertReplaceEdit {
10432                        new_text: "saturating_sub()".to_owned(),
10433                        insert: lsp::Range::new(
10434                            lsp::Position::new(7, 7),
10435                            lsp::Position::new(7, 11),
10436                        ),
10437                        replace: lsp::Range::new(
10438                            lsp::Position::new(7, 7),
10439                            lsp::Position::new(7, 13),
10440                        ),
10441                    },
10442                )),
10443                ..lsp::CompletionItem::default()
10444            };
10445
10446            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10447        })
10448        .next()
10449        .await
10450        .unwrap();
10451
10452    cx.condition(&editor, |editor, _| editor.context_menu_visible())
10453        .await;
10454
10455    editor
10456        .update_in(cx, |editor, window, cx| {
10457            editor
10458                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10459                .unwrap()
10460        })
10461        .await
10462        .unwrap();
10463
10464    editor.update(cx, |editor, cx| {
10465        assert_text_with_selections(editor, expected_multibuffer, cx);
10466    })
10467}
10468
10469#[gpui::test]
10470async fn test_completion(cx: &mut TestAppContext) {
10471    init_test(cx, |_| {});
10472
10473    let mut cx = EditorLspTestContext::new_rust(
10474        lsp::ServerCapabilities {
10475            completion_provider: Some(lsp::CompletionOptions {
10476                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10477                resolve_provider: Some(true),
10478                ..Default::default()
10479            }),
10480            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10481            ..Default::default()
10482        },
10483        cx,
10484    )
10485    .await;
10486    let counter = Arc::new(AtomicUsize::new(0));
10487
10488    cx.set_state(indoc! {"
10489        oneˇ
10490        two
10491        three
10492    "});
10493    cx.simulate_keystroke(".");
10494    handle_completion_request(
10495        &mut cx,
10496        indoc! {"
10497            one.|<>
10498            two
10499            three
10500        "},
10501        vec!["first_completion", "second_completion"],
10502        counter.clone(),
10503    )
10504    .await;
10505    cx.condition(|editor, _| editor.context_menu_visible())
10506        .await;
10507    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10508
10509    let _handler = handle_signature_help_request(
10510        &mut cx,
10511        lsp::SignatureHelp {
10512            signatures: vec![lsp::SignatureInformation {
10513                label: "test signature".to_string(),
10514                documentation: None,
10515                parameters: Some(vec![lsp::ParameterInformation {
10516                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10517                    documentation: None,
10518                }]),
10519                active_parameter: None,
10520            }],
10521            active_signature: None,
10522            active_parameter: None,
10523        },
10524    );
10525    cx.update_editor(|editor, window, cx| {
10526        assert!(
10527            !editor.signature_help_state.is_shown(),
10528            "No signature help was called for"
10529        );
10530        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10531    });
10532    cx.run_until_parked();
10533    cx.update_editor(|editor, _, _| {
10534        assert!(
10535            !editor.signature_help_state.is_shown(),
10536            "No signature help should be shown when completions menu is open"
10537        );
10538    });
10539
10540    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10541        editor.context_menu_next(&Default::default(), window, cx);
10542        editor
10543            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10544            .unwrap()
10545    });
10546    cx.assert_editor_state(indoc! {"
10547        one.second_completionˇ
10548        two
10549        three
10550    "});
10551
10552    handle_resolve_completion_request(
10553        &mut cx,
10554        Some(vec![
10555            (
10556                //This overlaps with the primary completion edit which is
10557                //misbehavior from the LSP spec, test that we filter it out
10558                indoc! {"
10559                    one.second_ˇcompletion
10560                    two
10561                    threeˇ
10562                "},
10563                "overlapping additional edit",
10564            ),
10565            (
10566                indoc! {"
10567                    one.second_completion
10568                    two
10569                    threeˇ
10570                "},
10571                "\nadditional edit",
10572            ),
10573        ]),
10574    )
10575    .await;
10576    apply_additional_edits.await.unwrap();
10577    cx.assert_editor_state(indoc! {"
10578        one.second_completionˇ
10579        two
10580        three
10581        additional edit
10582    "});
10583
10584    cx.set_state(indoc! {"
10585        one.second_completion
10586        twoˇ
10587        threeˇ
10588        additional edit
10589    "});
10590    cx.simulate_keystroke(" ");
10591    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10592    cx.simulate_keystroke("s");
10593    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10594
10595    cx.assert_editor_state(indoc! {"
10596        one.second_completion
10597        two sˇ
10598        three sˇ
10599        additional edit
10600    "});
10601    handle_completion_request(
10602        &mut cx,
10603        indoc! {"
10604            one.second_completion
10605            two s
10606            three <s|>
10607            additional edit
10608        "},
10609        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10610        counter.clone(),
10611    )
10612    .await;
10613    cx.condition(|editor, _| editor.context_menu_visible())
10614        .await;
10615    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10616
10617    cx.simulate_keystroke("i");
10618
10619    handle_completion_request(
10620        &mut cx,
10621        indoc! {"
10622            one.second_completion
10623            two si
10624            three <si|>
10625            additional edit
10626        "},
10627        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10628        counter.clone(),
10629    )
10630    .await;
10631    cx.condition(|editor, _| editor.context_menu_visible())
10632        .await;
10633    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10634
10635    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10636        editor
10637            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10638            .unwrap()
10639    });
10640    cx.assert_editor_state(indoc! {"
10641        one.second_completion
10642        two sixth_completionˇ
10643        three sixth_completionˇ
10644        additional edit
10645    "});
10646
10647    apply_additional_edits.await.unwrap();
10648
10649    update_test_language_settings(&mut cx, |settings| {
10650        settings.defaults.show_completions_on_input = Some(false);
10651    });
10652    cx.set_state("editorˇ");
10653    cx.simulate_keystroke(".");
10654    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10655    cx.simulate_keystrokes("c l o");
10656    cx.assert_editor_state("editor.cloˇ");
10657    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10658    cx.update_editor(|editor, window, cx| {
10659        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10660    });
10661    handle_completion_request(
10662        &mut cx,
10663        "editor.<clo|>",
10664        vec!["close", "clobber"],
10665        counter.clone(),
10666    )
10667    .await;
10668    cx.condition(|editor, _| editor.context_menu_visible())
10669        .await;
10670    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10671
10672    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10673        editor
10674            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10675            .unwrap()
10676    });
10677    cx.assert_editor_state("editor.closeˇ");
10678    handle_resolve_completion_request(&mut cx, None).await;
10679    apply_additional_edits.await.unwrap();
10680}
10681
10682#[gpui::test]
10683async fn test_word_completion(cx: &mut TestAppContext) {
10684    let lsp_fetch_timeout_ms = 10;
10685    init_test(cx, |language_settings| {
10686        language_settings.defaults.completions = Some(CompletionSettings {
10687            words: WordsCompletionMode::Fallback,
10688            lsp: true,
10689            lsp_fetch_timeout_ms: 10,
10690            lsp_insert_mode: LspInsertMode::Insert,
10691        });
10692    });
10693
10694    let mut cx = EditorLspTestContext::new_rust(
10695        lsp::ServerCapabilities {
10696            completion_provider: Some(lsp::CompletionOptions {
10697                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10698                ..lsp::CompletionOptions::default()
10699            }),
10700            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10701            ..lsp::ServerCapabilities::default()
10702        },
10703        cx,
10704    )
10705    .await;
10706
10707    let throttle_completions = Arc::new(AtomicBool::new(false));
10708
10709    let lsp_throttle_completions = throttle_completions.clone();
10710    let _completion_requests_handler =
10711        cx.lsp
10712            .server
10713            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10714                let lsp_throttle_completions = lsp_throttle_completions.clone();
10715                let cx = cx.clone();
10716                async move {
10717                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10718                        cx.background_executor()
10719                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10720                            .await;
10721                    }
10722                    Ok(Some(lsp::CompletionResponse::Array(vec![
10723                        lsp::CompletionItem {
10724                            label: "first".into(),
10725                            ..lsp::CompletionItem::default()
10726                        },
10727                        lsp::CompletionItem {
10728                            label: "last".into(),
10729                            ..lsp::CompletionItem::default()
10730                        },
10731                    ])))
10732                }
10733            });
10734
10735    cx.set_state(indoc! {"
10736        oneˇ
10737        two
10738        three
10739    "});
10740    cx.simulate_keystroke(".");
10741    cx.executor().run_until_parked();
10742    cx.condition(|editor, _| editor.context_menu_visible())
10743        .await;
10744    cx.update_editor(|editor, window, cx| {
10745        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10746        {
10747            assert_eq!(
10748                completion_menu_entries(&menu),
10749                &["first", "last"],
10750                "When LSP server is fast to reply, no fallback word completions are used"
10751            );
10752        } else {
10753            panic!("expected completion menu to be open");
10754        }
10755        editor.cancel(&Cancel, window, cx);
10756    });
10757    cx.executor().run_until_parked();
10758    cx.condition(|editor, _| !editor.context_menu_visible())
10759        .await;
10760
10761    throttle_completions.store(true, atomic::Ordering::Release);
10762    cx.simulate_keystroke(".");
10763    cx.executor()
10764        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10765    cx.executor().run_until_parked();
10766    cx.condition(|editor, _| editor.context_menu_visible())
10767        .await;
10768    cx.update_editor(|editor, _, _| {
10769        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10770        {
10771            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10772                "When LSP server is slow, document words can be shown instead, if configured accordingly");
10773        } else {
10774            panic!("expected completion menu to be open");
10775        }
10776    });
10777}
10778
10779#[gpui::test]
10780async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10781    init_test(cx, |language_settings| {
10782        language_settings.defaults.completions = Some(CompletionSettings {
10783            words: WordsCompletionMode::Enabled,
10784            lsp: true,
10785            lsp_fetch_timeout_ms: 0,
10786            lsp_insert_mode: LspInsertMode::Insert,
10787        });
10788    });
10789
10790    let mut cx = EditorLspTestContext::new_rust(
10791        lsp::ServerCapabilities {
10792            completion_provider: Some(lsp::CompletionOptions {
10793                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10794                ..lsp::CompletionOptions::default()
10795            }),
10796            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10797            ..lsp::ServerCapabilities::default()
10798        },
10799        cx,
10800    )
10801    .await;
10802
10803    let _completion_requests_handler =
10804        cx.lsp
10805            .server
10806            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10807                Ok(Some(lsp::CompletionResponse::Array(vec![
10808                    lsp::CompletionItem {
10809                        label: "first".into(),
10810                        ..lsp::CompletionItem::default()
10811                    },
10812                    lsp::CompletionItem {
10813                        label: "last".into(),
10814                        ..lsp::CompletionItem::default()
10815                    },
10816                ])))
10817            });
10818
10819    cx.set_state(indoc! {"ˇ
10820        first
10821        last
10822        second
10823    "});
10824    cx.simulate_keystroke(".");
10825    cx.executor().run_until_parked();
10826    cx.condition(|editor, _| editor.context_menu_visible())
10827        .await;
10828    cx.update_editor(|editor, _, _| {
10829        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10830        {
10831            assert_eq!(
10832                completion_menu_entries(&menu),
10833                &["first", "last", "second"],
10834                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10835            );
10836        } else {
10837            panic!("expected completion menu to be open");
10838        }
10839    });
10840}
10841
10842#[gpui::test]
10843async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10844    init_test(cx, |language_settings| {
10845        language_settings.defaults.completions = Some(CompletionSettings {
10846            words: WordsCompletionMode::Disabled,
10847            lsp: true,
10848            lsp_fetch_timeout_ms: 0,
10849            lsp_insert_mode: LspInsertMode::Insert,
10850        });
10851    });
10852
10853    let mut cx = EditorLspTestContext::new_rust(
10854        lsp::ServerCapabilities {
10855            completion_provider: Some(lsp::CompletionOptions {
10856                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10857                ..lsp::CompletionOptions::default()
10858            }),
10859            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10860            ..lsp::ServerCapabilities::default()
10861        },
10862        cx,
10863    )
10864    .await;
10865
10866    let _completion_requests_handler =
10867        cx.lsp
10868            .server
10869            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10870                panic!("LSP completions should not be queried when dealing with word completions")
10871            });
10872
10873    cx.set_state(indoc! {"ˇ
10874        first
10875        last
10876        second
10877    "});
10878    cx.update_editor(|editor, window, cx| {
10879        editor.show_word_completions(&ShowWordCompletions, window, cx);
10880    });
10881    cx.executor().run_until_parked();
10882    cx.condition(|editor, _| editor.context_menu_visible())
10883        .await;
10884    cx.update_editor(|editor, _, _| {
10885        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10886        {
10887            assert_eq!(
10888                completion_menu_entries(&menu),
10889                &["first", "last", "second"],
10890                "`ShowWordCompletions` action should show word completions"
10891            );
10892        } else {
10893            panic!("expected completion menu to be open");
10894        }
10895    });
10896
10897    cx.simulate_keystroke("l");
10898    cx.executor().run_until_parked();
10899    cx.condition(|editor, _| editor.context_menu_visible())
10900        .await;
10901    cx.update_editor(|editor, _, _| {
10902        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10903        {
10904            assert_eq!(
10905                completion_menu_entries(&menu),
10906                &["last"],
10907                "After showing word completions, further editing should filter them and not query the LSP"
10908            );
10909        } else {
10910            panic!("expected completion menu to be open");
10911        }
10912    });
10913}
10914
10915#[gpui::test]
10916async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10917    init_test(cx, |language_settings| {
10918        language_settings.defaults.completions = Some(CompletionSettings {
10919            words: WordsCompletionMode::Fallback,
10920            lsp: false,
10921            lsp_fetch_timeout_ms: 0,
10922            lsp_insert_mode: LspInsertMode::Insert,
10923        });
10924    });
10925
10926    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10927
10928    cx.set_state(indoc! {"ˇ
10929        0_usize
10930        let
10931        33
10932        4.5f32
10933    "});
10934    cx.update_editor(|editor, window, cx| {
10935        editor.show_completions(&ShowCompletions::default(), window, cx);
10936    });
10937    cx.executor().run_until_parked();
10938    cx.condition(|editor, _| editor.context_menu_visible())
10939        .await;
10940    cx.update_editor(|editor, window, cx| {
10941        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10942        {
10943            assert_eq!(
10944                completion_menu_entries(&menu),
10945                &["let"],
10946                "With no digits in the completion query, no digits should be in the word completions"
10947            );
10948        } else {
10949            panic!("expected completion menu to be open");
10950        }
10951        editor.cancel(&Cancel, window, cx);
10952    });
10953
10954    cx.set_state(indoc! {"10955        0_usize
10956        let
10957        3
10958        33.35f32
10959    "});
10960    cx.update_editor(|editor, window, cx| {
10961        editor.show_completions(&ShowCompletions::default(), window, cx);
10962    });
10963    cx.executor().run_until_parked();
10964    cx.condition(|editor, _| editor.context_menu_visible())
10965        .await;
10966    cx.update_editor(|editor, _, _| {
10967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10968        {
10969            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
10970                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
10971        } else {
10972            panic!("expected completion menu to be open");
10973        }
10974    });
10975}
10976
10977fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
10978    let position = || lsp::Position {
10979        line: params.text_document_position.position.line,
10980        character: params.text_document_position.position.character,
10981    };
10982    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10983        range: lsp::Range {
10984            start: position(),
10985            end: position(),
10986        },
10987        new_text: text.to_string(),
10988    }))
10989}
10990
10991#[gpui::test]
10992async fn test_multiline_completion(cx: &mut TestAppContext) {
10993    init_test(cx, |_| {});
10994
10995    let fs = FakeFs::new(cx.executor());
10996    fs.insert_tree(
10997        path!("/a"),
10998        json!({
10999            "main.ts": "a",
11000        }),
11001    )
11002    .await;
11003
11004    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11005    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11006    let typescript_language = Arc::new(Language::new(
11007        LanguageConfig {
11008            name: "TypeScript".into(),
11009            matcher: LanguageMatcher {
11010                path_suffixes: vec!["ts".to_string()],
11011                ..LanguageMatcher::default()
11012            },
11013            line_comments: vec!["// ".into()],
11014            ..LanguageConfig::default()
11015        },
11016        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11017    ));
11018    language_registry.add(typescript_language.clone());
11019    let mut fake_servers = language_registry.register_fake_lsp(
11020        "TypeScript",
11021        FakeLspAdapter {
11022            capabilities: lsp::ServerCapabilities {
11023                completion_provider: Some(lsp::CompletionOptions {
11024                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11025                    ..lsp::CompletionOptions::default()
11026                }),
11027                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11028                ..lsp::ServerCapabilities::default()
11029            },
11030            // Emulate vtsls label generation
11031            label_for_completion: Some(Box::new(|item, _| {
11032                let text = if let Some(description) = item
11033                    .label_details
11034                    .as_ref()
11035                    .and_then(|label_details| label_details.description.as_ref())
11036                {
11037                    format!("{} {}", item.label, description)
11038                } else if let Some(detail) = &item.detail {
11039                    format!("{} {}", item.label, detail)
11040                } else {
11041                    item.label.clone()
11042                };
11043                let len = text.len();
11044                Some(language::CodeLabel {
11045                    text,
11046                    runs: Vec::new(),
11047                    filter_range: 0..len,
11048                })
11049            })),
11050            ..FakeLspAdapter::default()
11051        },
11052    );
11053    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11054    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11055    let worktree_id = workspace
11056        .update(cx, |workspace, _window, cx| {
11057            workspace.project().update(cx, |project, cx| {
11058                project.worktrees(cx).next().unwrap().read(cx).id()
11059            })
11060        })
11061        .unwrap();
11062    let _buffer = project
11063        .update(cx, |project, cx| {
11064            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11065        })
11066        .await
11067        .unwrap();
11068    let editor = workspace
11069        .update(cx, |workspace, window, cx| {
11070            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11071        })
11072        .unwrap()
11073        .await
11074        .unwrap()
11075        .downcast::<Editor>()
11076        .unwrap();
11077    let fake_server = fake_servers.next().await.unwrap();
11078
11079    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11080    let multiline_label_2 = "a\nb\nc\n";
11081    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11082    let multiline_description = "d\ne\nf\n";
11083    let multiline_detail_2 = "g\nh\ni\n";
11084
11085    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11086        move |params, _| async move {
11087            Ok(Some(lsp::CompletionResponse::Array(vec![
11088                lsp::CompletionItem {
11089                    label: multiline_label.to_string(),
11090                    text_edit: gen_text_edit(&params, "new_text_1"),
11091                    ..lsp::CompletionItem::default()
11092                },
11093                lsp::CompletionItem {
11094                    label: "single line label 1".to_string(),
11095                    detail: Some(multiline_detail.to_string()),
11096                    text_edit: gen_text_edit(&params, "new_text_2"),
11097                    ..lsp::CompletionItem::default()
11098                },
11099                lsp::CompletionItem {
11100                    label: "single line label 2".to_string(),
11101                    label_details: Some(lsp::CompletionItemLabelDetails {
11102                        description: Some(multiline_description.to_string()),
11103                        detail: None,
11104                    }),
11105                    text_edit: gen_text_edit(&params, "new_text_2"),
11106                    ..lsp::CompletionItem::default()
11107                },
11108                lsp::CompletionItem {
11109                    label: multiline_label_2.to_string(),
11110                    detail: Some(multiline_detail_2.to_string()),
11111                    text_edit: gen_text_edit(&params, "new_text_3"),
11112                    ..lsp::CompletionItem::default()
11113                },
11114                lsp::CompletionItem {
11115                    label: "Label with many     spaces and \t but without newlines".to_string(),
11116                    detail: Some(
11117                        "Details with many     spaces and \t but without newlines".to_string(),
11118                    ),
11119                    text_edit: gen_text_edit(&params, "new_text_4"),
11120                    ..lsp::CompletionItem::default()
11121                },
11122            ])))
11123        },
11124    );
11125
11126    editor.update_in(cx, |editor, window, cx| {
11127        cx.focus_self(window);
11128        editor.move_to_end(&MoveToEnd, window, cx);
11129        editor.handle_input(".", window, cx);
11130    });
11131    cx.run_until_parked();
11132    completion_handle.next().await.unwrap();
11133
11134    editor.update(cx, |editor, _| {
11135        assert!(editor.context_menu_visible());
11136        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11137        {
11138            let completion_labels = menu
11139                .completions
11140                .borrow()
11141                .iter()
11142                .map(|c| c.label.text.clone())
11143                .collect::<Vec<_>>();
11144            assert_eq!(
11145                completion_labels,
11146                &[
11147                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11148                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11149                    "single line label 2 d e f ",
11150                    "a b c g h i ",
11151                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11152                ],
11153                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11154            );
11155
11156            for completion in menu
11157                .completions
11158                .borrow()
11159                .iter() {
11160                    assert_eq!(
11161                        completion.label.filter_range,
11162                        0..completion.label.text.len(),
11163                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11164                    );
11165                }
11166        } else {
11167            panic!("expected completion menu to be open");
11168        }
11169    });
11170}
11171
11172#[gpui::test]
11173async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11174    init_test(cx, |_| {});
11175    let mut cx = EditorLspTestContext::new_rust(
11176        lsp::ServerCapabilities {
11177            completion_provider: Some(lsp::CompletionOptions {
11178                trigger_characters: Some(vec![".".to_string()]),
11179                ..Default::default()
11180            }),
11181            ..Default::default()
11182        },
11183        cx,
11184    )
11185    .await;
11186    cx.lsp
11187        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11188            Ok(Some(lsp::CompletionResponse::Array(vec![
11189                lsp::CompletionItem {
11190                    label: "first".into(),
11191                    ..Default::default()
11192                },
11193                lsp::CompletionItem {
11194                    label: "last".into(),
11195                    ..Default::default()
11196                },
11197            ])))
11198        });
11199    cx.set_state("variableˇ");
11200    cx.simulate_keystroke(".");
11201    cx.executor().run_until_parked();
11202
11203    cx.update_editor(|editor, _, _| {
11204        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11205        {
11206            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11207        } else {
11208            panic!("expected completion menu to be open");
11209        }
11210    });
11211
11212    cx.update_editor(|editor, window, cx| {
11213        editor.move_page_down(&MovePageDown::default(), window, cx);
11214        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11215        {
11216            assert!(
11217                menu.selected_item == 1,
11218                "expected PageDown to select the last item from the context menu"
11219            );
11220        } else {
11221            panic!("expected completion menu to stay open after PageDown");
11222        }
11223    });
11224
11225    cx.update_editor(|editor, window, cx| {
11226        editor.move_page_up(&MovePageUp::default(), window, cx);
11227        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11228        {
11229            assert!(
11230                menu.selected_item == 0,
11231                "expected PageUp to select the first item from the context menu"
11232            );
11233        } else {
11234            panic!("expected completion menu to stay open after PageUp");
11235        }
11236    });
11237}
11238
11239#[gpui::test]
11240async fn test_completion_sort(cx: &mut TestAppContext) {
11241    init_test(cx, |_| {});
11242    let mut cx = EditorLspTestContext::new_rust(
11243        lsp::ServerCapabilities {
11244            completion_provider: Some(lsp::CompletionOptions {
11245                trigger_characters: Some(vec![".".to_string()]),
11246                ..Default::default()
11247            }),
11248            ..Default::default()
11249        },
11250        cx,
11251    )
11252    .await;
11253    cx.lsp
11254        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11255            Ok(Some(lsp::CompletionResponse::Array(vec![
11256                lsp::CompletionItem {
11257                    label: "Range".into(),
11258                    sort_text: Some("a".into()),
11259                    ..Default::default()
11260                },
11261                lsp::CompletionItem {
11262                    label: "r".into(),
11263                    sort_text: Some("b".into()),
11264                    ..Default::default()
11265                },
11266                lsp::CompletionItem {
11267                    label: "ret".into(),
11268                    sort_text: Some("c".into()),
11269                    ..Default::default()
11270                },
11271                lsp::CompletionItem {
11272                    label: "return".into(),
11273                    sort_text: Some("d".into()),
11274                    ..Default::default()
11275                },
11276                lsp::CompletionItem {
11277                    label: "slice".into(),
11278                    sort_text: Some("d".into()),
11279                    ..Default::default()
11280                },
11281            ])))
11282        });
11283    cx.set_state("");
11284    cx.executor().run_until_parked();
11285    cx.update_editor(|editor, window, cx| {
11286        editor.show_completions(
11287            &ShowCompletions {
11288                trigger: Some("r".into()),
11289            },
11290            window,
11291            cx,
11292        );
11293    });
11294    cx.executor().run_until_parked();
11295
11296    cx.update_editor(|editor, _, _| {
11297        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11298        {
11299            assert_eq!(
11300                completion_menu_entries(&menu),
11301                &["r", "ret", "Range", "return"]
11302            );
11303        } else {
11304            panic!("expected completion menu to be open");
11305        }
11306    });
11307}
11308
11309#[gpui::test]
11310async fn test_as_is_completions(cx: &mut TestAppContext) {
11311    init_test(cx, |_| {});
11312    let mut cx = EditorLspTestContext::new_rust(
11313        lsp::ServerCapabilities {
11314            completion_provider: Some(lsp::CompletionOptions {
11315                ..Default::default()
11316            }),
11317            ..Default::default()
11318        },
11319        cx,
11320    )
11321    .await;
11322    cx.lsp
11323        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11324            Ok(Some(lsp::CompletionResponse::Array(vec![
11325                lsp::CompletionItem {
11326                    label: "unsafe".into(),
11327                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11328                        range: lsp::Range {
11329                            start: lsp::Position {
11330                                line: 1,
11331                                character: 2,
11332                            },
11333                            end: lsp::Position {
11334                                line: 1,
11335                                character: 3,
11336                            },
11337                        },
11338                        new_text: "unsafe".to_string(),
11339                    })),
11340                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11341                    ..Default::default()
11342                },
11343            ])))
11344        });
11345    cx.set_state("fn a() {}\n");
11346    cx.executor().run_until_parked();
11347    cx.update_editor(|editor, window, cx| {
11348        editor.show_completions(
11349            &ShowCompletions {
11350                trigger: Some("\n".into()),
11351            },
11352            window,
11353            cx,
11354        );
11355    });
11356    cx.executor().run_until_parked();
11357
11358    cx.update_editor(|editor, window, cx| {
11359        editor.confirm_completion(&Default::default(), window, cx)
11360    });
11361    cx.executor().run_until_parked();
11362    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11363}
11364
11365#[gpui::test]
11366async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11367    init_test(cx, |_| {});
11368
11369    let mut cx = EditorLspTestContext::new_rust(
11370        lsp::ServerCapabilities {
11371            completion_provider: Some(lsp::CompletionOptions {
11372                trigger_characters: Some(vec![".".to_string()]),
11373                resolve_provider: Some(true),
11374                ..Default::default()
11375            }),
11376            ..Default::default()
11377        },
11378        cx,
11379    )
11380    .await;
11381
11382    cx.set_state("fn main() { let a = 2ˇ; }");
11383    cx.simulate_keystroke(".");
11384    let completion_item = lsp::CompletionItem {
11385        label: "Some".into(),
11386        kind: Some(lsp::CompletionItemKind::SNIPPET),
11387        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11388        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11389            kind: lsp::MarkupKind::Markdown,
11390            value: "```rust\nSome(2)\n```".to_string(),
11391        })),
11392        deprecated: Some(false),
11393        sort_text: Some("Some".to_string()),
11394        filter_text: Some("Some".to_string()),
11395        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11396        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11397            range: lsp::Range {
11398                start: lsp::Position {
11399                    line: 0,
11400                    character: 22,
11401                },
11402                end: lsp::Position {
11403                    line: 0,
11404                    character: 22,
11405                },
11406            },
11407            new_text: "Some(2)".to_string(),
11408        })),
11409        additional_text_edits: Some(vec![lsp::TextEdit {
11410            range: lsp::Range {
11411                start: lsp::Position {
11412                    line: 0,
11413                    character: 20,
11414                },
11415                end: lsp::Position {
11416                    line: 0,
11417                    character: 22,
11418                },
11419            },
11420            new_text: "".to_string(),
11421        }]),
11422        ..Default::default()
11423    };
11424
11425    let closure_completion_item = completion_item.clone();
11426    let counter = Arc::new(AtomicUsize::new(0));
11427    let counter_clone = counter.clone();
11428    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11429        let task_completion_item = closure_completion_item.clone();
11430        counter_clone.fetch_add(1, atomic::Ordering::Release);
11431        async move {
11432            Ok(Some(lsp::CompletionResponse::Array(vec![
11433                task_completion_item,
11434            ])))
11435        }
11436    });
11437
11438    cx.condition(|editor, _| editor.context_menu_visible())
11439        .await;
11440    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11441    assert!(request.next().await.is_some());
11442    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11443
11444    cx.simulate_keystrokes("S o m");
11445    cx.condition(|editor, _| editor.context_menu_visible())
11446        .await;
11447    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11448    assert!(request.next().await.is_some());
11449    assert!(request.next().await.is_some());
11450    assert!(request.next().await.is_some());
11451    request.close();
11452    assert!(request.next().await.is_none());
11453    assert_eq!(
11454        counter.load(atomic::Ordering::Acquire),
11455        4,
11456        "With the completions menu open, only one LSP request should happen per input"
11457    );
11458}
11459
11460#[gpui::test]
11461async fn test_toggle_comment(cx: &mut TestAppContext) {
11462    init_test(cx, |_| {});
11463    let mut cx = EditorTestContext::new(cx).await;
11464    let language = Arc::new(Language::new(
11465        LanguageConfig {
11466            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11467            ..Default::default()
11468        },
11469        Some(tree_sitter_rust::LANGUAGE.into()),
11470    ));
11471    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11472
11473    // If multiple selections intersect a line, the line is only toggled once.
11474    cx.set_state(indoc! {"
11475        fn a() {
11476            «//b();
11477            ˇ»// «c();
11478            //ˇ»  d();
11479        }
11480    "});
11481
11482    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11483
11484    cx.assert_editor_state(indoc! {"
11485        fn a() {
11486            «b();
11487            c();
11488            ˇ» d();
11489        }
11490    "});
11491
11492    // The comment prefix is inserted at the same column for every line in a
11493    // selection.
11494    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11495
11496    cx.assert_editor_state(indoc! {"
11497        fn a() {
11498            // «b();
11499            // c();
11500            ˇ»//  d();
11501        }
11502    "});
11503
11504    // If a selection ends at the beginning of a line, that line is not toggled.
11505    cx.set_selections_state(indoc! {"
11506        fn a() {
11507            // b();
11508            «// c();
11509        ˇ»    //  d();
11510        }
11511    "});
11512
11513    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11514
11515    cx.assert_editor_state(indoc! {"
11516        fn a() {
11517            // b();
11518            «c();
11519        ˇ»    //  d();
11520        }
11521    "});
11522
11523    // If a selection span a single line and is empty, the line is toggled.
11524    cx.set_state(indoc! {"
11525        fn a() {
11526            a();
11527            b();
11528        ˇ
11529        }
11530    "});
11531
11532    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11533
11534    cx.assert_editor_state(indoc! {"
11535        fn a() {
11536            a();
11537            b();
11538        //•ˇ
11539        }
11540    "});
11541
11542    // If a selection span multiple lines, empty lines are not toggled.
11543    cx.set_state(indoc! {"
11544        fn a() {
11545            «a();
11546
11547            c();ˇ»
11548        }
11549    "});
11550
11551    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11552
11553    cx.assert_editor_state(indoc! {"
11554        fn a() {
11555            // «a();
11556
11557            // c();ˇ»
11558        }
11559    "});
11560
11561    // If a selection includes multiple comment prefixes, all lines are uncommented.
11562    cx.set_state(indoc! {"
11563        fn a() {
11564            «// a();
11565            /// b();
11566            //! c();ˇ»
11567        }
11568    "});
11569
11570    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11571
11572    cx.assert_editor_state(indoc! {"
11573        fn a() {
11574            «a();
11575            b();
11576            c();ˇ»
11577        }
11578    "});
11579}
11580
11581#[gpui::test]
11582async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11583    init_test(cx, |_| {});
11584    let mut cx = EditorTestContext::new(cx).await;
11585    let language = Arc::new(Language::new(
11586        LanguageConfig {
11587            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11588            ..Default::default()
11589        },
11590        Some(tree_sitter_rust::LANGUAGE.into()),
11591    ));
11592    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11593
11594    let toggle_comments = &ToggleComments {
11595        advance_downwards: false,
11596        ignore_indent: true,
11597    };
11598
11599    // If multiple selections intersect a line, the line is only toggled once.
11600    cx.set_state(indoc! {"
11601        fn a() {
11602        //    «b();
11603        //    c();
11604        //    ˇ» d();
11605        }
11606    "});
11607
11608    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11609
11610    cx.assert_editor_state(indoc! {"
11611        fn a() {
11612            «b();
11613            c();
11614            ˇ» d();
11615        }
11616    "});
11617
11618    // The comment prefix is inserted at the beginning of each line
11619    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11620
11621    cx.assert_editor_state(indoc! {"
11622        fn a() {
11623        //    «b();
11624        //    c();
11625        //    ˇ» d();
11626        }
11627    "});
11628
11629    // If a selection ends at the beginning of a line, that line is not toggled.
11630    cx.set_selections_state(indoc! {"
11631        fn a() {
11632        //    b();
11633        //    «c();
11634        ˇ»//     d();
11635        }
11636    "});
11637
11638    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11639
11640    cx.assert_editor_state(indoc! {"
11641        fn a() {
11642        //    b();
11643            «c();
11644        ˇ»//     d();
11645        }
11646    "});
11647
11648    // If a selection span a single line and is empty, the line is toggled.
11649    cx.set_state(indoc! {"
11650        fn a() {
11651            a();
11652            b();
11653        ˇ
11654        }
11655    "});
11656
11657    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11658
11659    cx.assert_editor_state(indoc! {"
11660        fn a() {
11661            a();
11662            b();
11663        //ˇ
11664        }
11665    "});
11666
11667    // If a selection span multiple lines, empty lines are not toggled.
11668    cx.set_state(indoc! {"
11669        fn a() {
11670            «a();
11671
11672            c();ˇ»
11673        }
11674    "});
11675
11676    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11677
11678    cx.assert_editor_state(indoc! {"
11679        fn a() {
11680        //    «a();
11681
11682        //    c();ˇ»
11683        }
11684    "});
11685
11686    // If a selection includes multiple comment prefixes, all lines are uncommented.
11687    cx.set_state(indoc! {"
11688        fn a() {
11689        //    «a();
11690        ///    b();
11691        //!    c();ˇ»
11692        }
11693    "});
11694
11695    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11696
11697    cx.assert_editor_state(indoc! {"
11698        fn a() {
11699            «a();
11700            b();
11701            c();ˇ»
11702        }
11703    "});
11704}
11705
11706#[gpui::test]
11707async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11708    init_test(cx, |_| {});
11709
11710    let language = Arc::new(Language::new(
11711        LanguageConfig {
11712            line_comments: vec!["// ".into()],
11713            ..Default::default()
11714        },
11715        Some(tree_sitter_rust::LANGUAGE.into()),
11716    ));
11717
11718    let mut cx = EditorTestContext::new(cx).await;
11719
11720    cx.language_registry().add(language.clone());
11721    cx.update_buffer(|buffer, cx| {
11722        buffer.set_language(Some(language), cx);
11723    });
11724
11725    let toggle_comments = &ToggleComments {
11726        advance_downwards: true,
11727        ignore_indent: false,
11728    };
11729
11730    // Single cursor on one line -> advance
11731    // Cursor moves horizontally 3 characters as well on non-blank line
11732    cx.set_state(indoc!(
11733        "fn a() {
11734             ˇdog();
11735             cat();
11736        }"
11737    ));
11738    cx.update_editor(|editor, window, cx| {
11739        editor.toggle_comments(toggle_comments, window, cx);
11740    });
11741    cx.assert_editor_state(indoc!(
11742        "fn a() {
11743             // dog();
11744             catˇ();
11745        }"
11746    ));
11747
11748    // Single selection on one line -> don't advance
11749    cx.set_state(indoc!(
11750        "fn a() {
11751             «dog()ˇ»;
11752             cat();
11753        }"
11754    ));
11755    cx.update_editor(|editor, window, cx| {
11756        editor.toggle_comments(toggle_comments, window, cx);
11757    });
11758    cx.assert_editor_state(indoc!(
11759        "fn a() {
11760             // «dog()ˇ»;
11761             cat();
11762        }"
11763    ));
11764
11765    // Multiple cursors on one line -> advance
11766    cx.set_state(indoc!(
11767        "fn a() {
11768             ˇdˇog();
11769             cat();
11770        }"
11771    ));
11772    cx.update_editor(|editor, window, cx| {
11773        editor.toggle_comments(toggle_comments, window, cx);
11774    });
11775    cx.assert_editor_state(indoc!(
11776        "fn a() {
11777             // dog();
11778             catˇ(ˇ);
11779        }"
11780    ));
11781
11782    // Multiple cursors on one line, with selection -> don't advance
11783    cx.set_state(indoc!(
11784        "fn a() {
11785             ˇdˇog«()ˇ»;
11786             cat();
11787        }"
11788    ));
11789    cx.update_editor(|editor, window, cx| {
11790        editor.toggle_comments(toggle_comments, window, cx);
11791    });
11792    cx.assert_editor_state(indoc!(
11793        "fn a() {
11794             // ˇdˇog«()ˇ»;
11795             cat();
11796        }"
11797    ));
11798
11799    // Single cursor on one line -> advance
11800    // Cursor moves to column 0 on blank line
11801    cx.set_state(indoc!(
11802        "fn a() {
11803             ˇdog();
11804
11805             cat();
11806        }"
11807    ));
11808    cx.update_editor(|editor, window, cx| {
11809        editor.toggle_comments(toggle_comments, window, cx);
11810    });
11811    cx.assert_editor_state(indoc!(
11812        "fn a() {
11813             // dog();
11814        ˇ
11815             cat();
11816        }"
11817    ));
11818
11819    // Single cursor on one line -> advance
11820    // Cursor starts and ends at column 0
11821    cx.set_state(indoc!(
11822        "fn a() {
11823         ˇ    dog();
11824             cat();
11825        }"
11826    ));
11827    cx.update_editor(|editor, window, cx| {
11828        editor.toggle_comments(toggle_comments, window, cx);
11829    });
11830    cx.assert_editor_state(indoc!(
11831        "fn a() {
11832             // dog();
11833         ˇ    cat();
11834        }"
11835    ));
11836}
11837
11838#[gpui::test]
11839async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11840    init_test(cx, |_| {});
11841
11842    let mut cx = EditorTestContext::new(cx).await;
11843
11844    let html_language = Arc::new(
11845        Language::new(
11846            LanguageConfig {
11847                name: "HTML".into(),
11848                block_comment: Some(("<!-- ".into(), " -->".into())),
11849                ..Default::default()
11850            },
11851            Some(tree_sitter_html::LANGUAGE.into()),
11852        )
11853        .with_injection_query(
11854            r#"
11855            (script_element
11856                (raw_text) @injection.content
11857                (#set! injection.language "javascript"))
11858            "#,
11859        )
11860        .unwrap(),
11861    );
11862
11863    let javascript_language = Arc::new(Language::new(
11864        LanguageConfig {
11865            name: "JavaScript".into(),
11866            line_comments: vec!["// ".into()],
11867            ..Default::default()
11868        },
11869        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11870    ));
11871
11872    cx.language_registry().add(html_language.clone());
11873    cx.language_registry().add(javascript_language.clone());
11874    cx.update_buffer(|buffer, cx| {
11875        buffer.set_language(Some(html_language), cx);
11876    });
11877
11878    // Toggle comments for empty selections
11879    cx.set_state(
11880        &r#"
11881            <p>A</p>ˇ
11882            <p>B</p>ˇ
11883            <p>C</p>ˇ
11884        "#
11885        .unindent(),
11886    );
11887    cx.update_editor(|editor, window, cx| {
11888        editor.toggle_comments(&ToggleComments::default(), window, cx)
11889    });
11890    cx.assert_editor_state(
11891        &r#"
11892            <!-- <p>A</p>ˇ -->
11893            <!-- <p>B</p>ˇ -->
11894            <!-- <p>C</p>ˇ -->
11895        "#
11896        .unindent(),
11897    );
11898    cx.update_editor(|editor, window, cx| {
11899        editor.toggle_comments(&ToggleComments::default(), window, cx)
11900    });
11901    cx.assert_editor_state(
11902        &r#"
11903            <p>A</p>ˇ
11904            <p>B</p>ˇ
11905            <p>C</p>ˇ
11906        "#
11907        .unindent(),
11908    );
11909
11910    // Toggle comments for mixture of empty and non-empty selections, where
11911    // multiple selections occupy a given line.
11912    cx.set_state(
11913        &r#"
11914            <p>A«</p>
11915            <p>ˇ»B</p>ˇ
11916            <p>C«</p>
11917            <p>ˇ»D</p>ˇ
11918        "#
11919        .unindent(),
11920    );
11921
11922    cx.update_editor(|editor, window, cx| {
11923        editor.toggle_comments(&ToggleComments::default(), window, cx)
11924    });
11925    cx.assert_editor_state(
11926        &r#"
11927            <!-- <p>A«</p>
11928            <p>ˇ»B</p>ˇ -->
11929            <!-- <p>C«</p>
11930            <p>ˇ»D</p>ˇ -->
11931        "#
11932        .unindent(),
11933    );
11934    cx.update_editor(|editor, window, cx| {
11935        editor.toggle_comments(&ToggleComments::default(), window, cx)
11936    });
11937    cx.assert_editor_state(
11938        &r#"
11939            <p>A«</p>
11940            <p>ˇ»B</p>ˇ
11941            <p>C«</p>
11942            <p>ˇ»D</p>ˇ
11943        "#
11944        .unindent(),
11945    );
11946
11947    // Toggle comments when different languages are active for different
11948    // selections.
11949    cx.set_state(
11950        &r#"
11951            ˇ<script>
11952                ˇvar x = new Y();
11953            ˇ</script>
11954        "#
11955        .unindent(),
11956    );
11957    cx.executor().run_until_parked();
11958    cx.update_editor(|editor, window, cx| {
11959        editor.toggle_comments(&ToggleComments::default(), window, cx)
11960    });
11961    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11962    // Uncommenting and commenting from this position brings in even more wrong artifacts.
11963    cx.assert_editor_state(
11964        &r#"
11965            <!-- ˇ<script> -->
11966                // ˇvar x = new Y();
11967            <!-- ˇ</script> -->
11968        "#
11969        .unindent(),
11970    );
11971}
11972
11973#[gpui::test]
11974fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11975    init_test(cx, |_| {});
11976
11977    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11978    let multibuffer = cx.new(|cx| {
11979        let mut multibuffer = MultiBuffer::new(ReadWrite);
11980        multibuffer.push_excerpts(
11981            buffer.clone(),
11982            [
11983                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11984                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11985            ],
11986            cx,
11987        );
11988        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11989        multibuffer
11990    });
11991
11992    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11993    editor.update_in(cx, |editor, window, cx| {
11994        assert_eq!(editor.text(cx), "aaaa\nbbbb");
11995        editor.change_selections(None, window, cx, |s| {
11996            s.select_ranges([
11997                Point::new(0, 0)..Point::new(0, 0),
11998                Point::new(1, 0)..Point::new(1, 0),
11999            ])
12000        });
12001
12002        editor.handle_input("X", window, cx);
12003        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12004        assert_eq!(
12005            editor.selections.ranges(cx),
12006            [
12007                Point::new(0, 1)..Point::new(0, 1),
12008                Point::new(1, 1)..Point::new(1, 1),
12009            ]
12010        );
12011
12012        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12013        editor.change_selections(None, window, cx, |s| {
12014            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12015        });
12016        editor.backspace(&Default::default(), window, cx);
12017        assert_eq!(editor.text(cx), "Xa\nbbb");
12018        assert_eq!(
12019            editor.selections.ranges(cx),
12020            [Point::new(1, 0)..Point::new(1, 0)]
12021        );
12022
12023        editor.change_selections(None, window, cx, |s| {
12024            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12025        });
12026        editor.backspace(&Default::default(), window, cx);
12027        assert_eq!(editor.text(cx), "X\nbb");
12028        assert_eq!(
12029            editor.selections.ranges(cx),
12030            [Point::new(0, 1)..Point::new(0, 1)]
12031        );
12032    });
12033}
12034
12035#[gpui::test]
12036fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12037    init_test(cx, |_| {});
12038
12039    let markers = vec![('[', ']').into(), ('(', ')').into()];
12040    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12041        indoc! {"
12042            [aaaa
12043            (bbbb]
12044            cccc)",
12045        },
12046        markers.clone(),
12047    );
12048    let excerpt_ranges = markers.into_iter().map(|marker| {
12049        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12050        ExcerptRange::new(context.clone())
12051    });
12052    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12053    let multibuffer = cx.new(|cx| {
12054        let mut multibuffer = MultiBuffer::new(ReadWrite);
12055        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12056        multibuffer
12057    });
12058
12059    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12060    editor.update_in(cx, |editor, window, cx| {
12061        let (expected_text, selection_ranges) = marked_text_ranges(
12062            indoc! {"
12063                aaaa
12064                bˇbbb
12065                bˇbbˇb
12066                cccc"
12067            },
12068            true,
12069        );
12070        assert_eq!(editor.text(cx), expected_text);
12071        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12072
12073        editor.handle_input("X", window, cx);
12074
12075        let (expected_text, expected_selections) = marked_text_ranges(
12076            indoc! {"
12077                aaaa
12078                bXˇbbXb
12079                bXˇbbXˇb
12080                cccc"
12081            },
12082            false,
12083        );
12084        assert_eq!(editor.text(cx), expected_text);
12085        assert_eq!(editor.selections.ranges(cx), expected_selections);
12086
12087        editor.newline(&Newline, window, cx);
12088        let (expected_text, expected_selections) = marked_text_ranges(
12089            indoc! {"
12090                aaaa
12091                bX
12092                ˇbbX
12093                b
12094                bX
12095                ˇbbX
12096                ˇb
12097                cccc"
12098            },
12099            false,
12100        );
12101        assert_eq!(editor.text(cx), expected_text);
12102        assert_eq!(editor.selections.ranges(cx), expected_selections);
12103    });
12104}
12105
12106#[gpui::test]
12107fn test_refresh_selections(cx: &mut TestAppContext) {
12108    init_test(cx, |_| {});
12109
12110    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12111    let mut excerpt1_id = None;
12112    let multibuffer = cx.new(|cx| {
12113        let mut multibuffer = MultiBuffer::new(ReadWrite);
12114        excerpt1_id = multibuffer
12115            .push_excerpts(
12116                buffer.clone(),
12117                [
12118                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12119                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12120                ],
12121                cx,
12122            )
12123            .into_iter()
12124            .next();
12125        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12126        multibuffer
12127    });
12128
12129    let editor = cx.add_window(|window, cx| {
12130        let mut editor = build_editor(multibuffer.clone(), window, cx);
12131        let snapshot = editor.snapshot(window, cx);
12132        editor.change_selections(None, window, cx, |s| {
12133            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12134        });
12135        editor.begin_selection(
12136            Point::new(2, 1).to_display_point(&snapshot),
12137            true,
12138            1,
12139            window,
12140            cx,
12141        );
12142        assert_eq!(
12143            editor.selections.ranges(cx),
12144            [
12145                Point::new(1, 3)..Point::new(1, 3),
12146                Point::new(2, 1)..Point::new(2, 1),
12147            ]
12148        );
12149        editor
12150    });
12151
12152    // Refreshing selections is a no-op when excerpts haven't changed.
12153    _ = editor.update(cx, |editor, window, cx| {
12154        editor.change_selections(None, window, cx, |s| s.refresh());
12155        assert_eq!(
12156            editor.selections.ranges(cx),
12157            [
12158                Point::new(1, 3)..Point::new(1, 3),
12159                Point::new(2, 1)..Point::new(2, 1),
12160            ]
12161        );
12162    });
12163
12164    multibuffer.update(cx, |multibuffer, cx| {
12165        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12166    });
12167    _ = editor.update(cx, |editor, window, cx| {
12168        // Removing an excerpt causes the first selection to become degenerate.
12169        assert_eq!(
12170            editor.selections.ranges(cx),
12171            [
12172                Point::new(0, 0)..Point::new(0, 0),
12173                Point::new(0, 1)..Point::new(0, 1)
12174            ]
12175        );
12176
12177        // Refreshing selections will relocate the first selection to the original buffer
12178        // location.
12179        editor.change_selections(None, window, cx, |s| s.refresh());
12180        assert_eq!(
12181            editor.selections.ranges(cx),
12182            [
12183                Point::new(0, 1)..Point::new(0, 1),
12184                Point::new(0, 3)..Point::new(0, 3)
12185            ]
12186        );
12187        assert!(editor.selections.pending_anchor().is_some());
12188    });
12189}
12190
12191#[gpui::test]
12192fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12193    init_test(cx, |_| {});
12194
12195    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12196    let mut excerpt1_id = None;
12197    let multibuffer = cx.new(|cx| {
12198        let mut multibuffer = MultiBuffer::new(ReadWrite);
12199        excerpt1_id = multibuffer
12200            .push_excerpts(
12201                buffer.clone(),
12202                [
12203                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12204                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12205                ],
12206                cx,
12207            )
12208            .into_iter()
12209            .next();
12210        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12211        multibuffer
12212    });
12213
12214    let editor = cx.add_window(|window, cx| {
12215        let mut editor = build_editor(multibuffer.clone(), window, cx);
12216        let snapshot = editor.snapshot(window, cx);
12217        editor.begin_selection(
12218            Point::new(1, 3).to_display_point(&snapshot),
12219            false,
12220            1,
12221            window,
12222            cx,
12223        );
12224        assert_eq!(
12225            editor.selections.ranges(cx),
12226            [Point::new(1, 3)..Point::new(1, 3)]
12227        );
12228        editor
12229    });
12230
12231    multibuffer.update(cx, |multibuffer, cx| {
12232        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12233    });
12234    _ = editor.update(cx, |editor, window, cx| {
12235        assert_eq!(
12236            editor.selections.ranges(cx),
12237            [Point::new(0, 0)..Point::new(0, 0)]
12238        );
12239
12240        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12241        editor.change_selections(None, window, cx, |s| s.refresh());
12242        assert_eq!(
12243            editor.selections.ranges(cx),
12244            [Point::new(0, 3)..Point::new(0, 3)]
12245        );
12246        assert!(editor.selections.pending_anchor().is_some());
12247    });
12248}
12249
12250#[gpui::test]
12251async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12252    init_test(cx, |_| {});
12253
12254    let language = Arc::new(
12255        Language::new(
12256            LanguageConfig {
12257                brackets: BracketPairConfig {
12258                    pairs: vec![
12259                        BracketPair {
12260                            start: "{".to_string(),
12261                            end: "}".to_string(),
12262                            close: true,
12263                            surround: true,
12264                            newline: true,
12265                        },
12266                        BracketPair {
12267                            start: "/* ".to_string(),
12268                            end: " */".to_string(),
12269                            close: true,
12270                            surround: true,
12271                            newline: true,
12272                        },
12273                    ],
12274                    ..Default::default()
12275                },
12276                ..Default::default()
12277            },
12278            Some(tree_sitter_rust::LANGUAGE.into()),
12279        )
12280        .with_indents_query("")
12281        .unwrap(),
12282    );
12283
12284    let text = concat!(
12285        "{   }\n",     //
12286        "  x\n",       //
12287        "  /*   */\n", //
12288        "x\n",         //
12289        "{{} }\n",     //
12290    );
12291
12292    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12293    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12294    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12295    editor
12296        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12297        .await;
12298
12299    editor.update_in(cx, |editor, window, cx| {
12300        editor.change_selections(None, window, cx, |s| {
12301            s.select_display_ranges([
12302                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12303                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12304                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12305            ])
12306        });
12307        editor.newline(&Newline, window, cx);
12308
12309        assert_eq!(
12310            editor.buffer().read(cx).read(cx).text(),
12311            concat!(
12312                "{ \n",    // Suppress rustfmt
12313                "\n",      //
12314                "}\n",     //
12315                "  x\n",   //
12316                "  /* \n", //
12317                "  \n",    //
12318                "  */\n",  //
12319                "x\n",     //
12320                "{{} \n",  //
12321                "}\n",     //
12322            )
12323        );
12324    });
12325}
12326
12327#[gpui::test]
12328fn test_highlighted_ranges(cx: &mut TestAppContext) {
12329    init_test(cx, |_| {});
12330
12331    let editor = cx.add_window(|window, cx| {
12332        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12333        build_editor(buffer.clone(), window, cx)
12334    });
12335
12336    _ = editor.update(cx, |editor, window, cx| {
12337        struct Type1;
12338        struct Type2;
12339
12340        let buffer = editor.buffer.read(cx).snapshot(cx);
12341
12342        let anchor_range =
12343            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12344
12345        editor.highlight_background::<Type1>(
12346            &[
12347                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12348                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12349                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12350                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12351            ],
12352            |_| Hsla::red(),
12353            cx,
12354        );
12355        editor.highlight_background::<Type2>(
12356            &[
12357                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12358                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12359                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12360                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12361            ],
12362            |_| Hsla::green(),
12363            cx,
12364        );
12365
12366        let snapshot = editor.snapshot(window, cx);
12367        let mut highlighted_ranges = editor.background_highlights_in_range(
12368            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12369            &snapshot,
12370            cx.theme().colors(),
12371        );
12372        // Enforce a consistent ordering based on color without relying on the ordering of the
12373        // highlight's `TypeId` which is non-executor.
12374        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12375        assert_eq!(
12376            highlighted_ranges,
12377            &[
12378                (
12379                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12380                    Hsla::red(),
12381                ),
12382                (
12383                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12384                    Hsla::red(),
12385                ),
12386                (
12387                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12388                    Hsla::green(),
12389                ),
12390                (
12391                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12392                    Hsla::green(),
12393                ),
12394            ]
12395        );
12396        assert_eq!(
12397            editor.background_highlights_in_range(
12398                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12399                &snapshot,
12400                cx.theme().colors(),
12401            ),
12402            &[(
12403                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12404                Hsla::red(),
12405            )]
12406        );
12407    });
12408}
12409
12410#[gpui::test]
12411async fn test_following(cx: &mut TestAppContext) {
12412    init_test(cx, |_| {});
12413
12414    let fs = FakeFs::new(cx.executor());
12415    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12416
12417    let buffer = project.update(cx, |project, cx| {
12418        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12419        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12420    });
12421    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12422    let follower = cx.update(|cx| {
12423        cx.open_window(
12424            WindowOptions {
12425                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12426                    gpui::Point::new(px(0.), px(0.)),
12427                    gpui::Point::new(px(10.), px(80.)),
12428                ))),
12429                ..Default::default()
12430            },
12431            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12432        )
12433        .unwrap()
12434    });
12435
12436    let is_still_following = Rc::new(RefCell::new(true));
12437    let follower_edit_event_count = Rc::new(RefCell::new(0));
12438    let pending_update = Rc::new(RefCell::new(None));
12439    let leader_entity = leader.root(cx).unwrap();
12440    let follower_entity = follower.root(cx).unwrap();
12441    _ = follower.update(cx, {
12442        let update = pending_update.clone();
12443        let is_still_following = is_still_following.clone();
12444        let follower_edit_event_count = follower_edit_event_count.clone();
12445        |_, window, cx| {
12446            cx.subscribe_in(
12447                &leader_entity,
12448                window,
12449                move |_, leader, event, window, cx| {
12450                    leader.read(cx).add_event_to_update_proto(
12451                        event,
12452                        &mut update.borrow_mut(),
12453                        window,
12454                        cx,
12455                    );
12456                },
12457            )
12458            .detach();
12459
12460            cx.subscribe_in(
12461                &follower_entity,
12462                window,
12463                move |_, _, event: &EditorEvent, _window, _cx| {
12464                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12465                        *is_still_following.borrow_mut() = false;
12466                    }
12467
12468                    if let EditorEvent::BufferEdited = event {
12469                        *follower_edit_event_count.borrow_mut() += 1;
12470                    }
12471                },
12472            )
12473            .detach();
12474        }
12475    });
12476
12477    // Update the selections only
12478    _ = leader.update(cx, |leader, window, cx| {
12479        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12480    });
12481    follower
12482        .update(cx, |follower, window, cx| {
12483            follower.apply_update_proto(
12484                &project,
12485                pending_update.borrow_mut().take().unwrap(),
12486                window,
12487                cx,
12488            )
12489        })
12490        .unwrap()
12491        .await
12492        .unwrap();
12493    _ = follower.update(cx, |follower, _, cx| {
12494        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12495    });
12496    assert!(*is_still_following.borrow());
12497    assert_eq!(*follower_edit_event_count.borrow(), 0);
12498
12499    // Update the scroll position only
12500    _ = leader.update(cx, |leader, window, cx| {
12501        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12502    });
12503    follower
12504        .update(cx, |follower, window, cx| {
12505            follower.apply_update_proto(
12506                &project,
12507                pending_update.borrow_mut().take().unwrap(),
12508                window,
12509                cx,
12510            )
12511        })
12512        .unwrap()
12513        .await
12514        .unwrap();
12515    assert_eq!(
12516        follower
12517            .update(cx, |follower, _, cx| follower.scroll_position(cx))
12518            .unwrap(),
12519        gpui::Point::new(1.5, 3.5)
12520    );
12521    assert!(*is_still_following.borrow());
12522    assert_eq!(*follower_edit_event_count.borrow(), 0);
12523
12524    // Update the selections and scroll position. The follower's scroll position is updated
12525    // via autoscroll, not via the leader's exact scroll position.
12526    _ = leader.update(cx, |leader, window, cx| {
12527        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12528        leader.request_autoscroll(Autoscroll::newest(), cx);
12529        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12530    });
12531    follower
12532        .update(cx, |follower, window, cx| {
12533            follower.apply_update_proto(
12534                &project,
12535                pending_update.borrow_mut().take().unwrap(),
12536                window,
12537                cx,
12538            )
12539        })
12540        .unwrap()
12541        .await
12542        .unwrap();
12543    _ = follower.update(cx, |follower, _, cx| {
12544        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12545        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12546    });
12547    assert!(*is_still_following.borrow());
12548
12549    // Creating a pending selection that precedes another selection
12550    _ = leader.update(cx, |leader, window, cx| {
12551        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12552        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12553    });
12554    follower
12555        .update(cx, |follower, window, cx| {
12556            follower.apply_update_proto(
12557                &project,
12558                pending_update.borrow_mut().take().unwrap(),
12559                window,
12560                cx,
12561            )
12562        })
12563        .unwrap()
12564        .await
12565        .unwrap();
12566    _ = follower.update(cx, |follower, _, cx| {
12567        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12568    });
12569    assert!(*is_still_following.borrow());
12570
12571    // Extend the pending selection so that it surrounds another selection
12572    _ = leader.update(cx, |leader, window, cx| {
12573        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12574    });
12575    follower
12576        .update(cx, |follower, window, cx| {
12577            follower.apply_update_proto(
12578                &project,
12579                pending_update.borrow_mut().take().unwrap(),
12580                window,
12581                cx,
12582            )
12583        })
12584        .unwrap()
12585        .await
12586        .unwrap();
12587    _ = follower.update(cx, |follower, _, cx| {
12588        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12589    });
12590
12591    // Scrolling locally breaks the follow
12592    _ = follower.update(cx, |follower, window, cx| {
12593        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12594        follower.set_scroll_anchor(
12595            ScrollAnchor {
12596                anchor: top_anchor,
12597                offset: gpui::Point::new(0.0, 0.5),
12598            },
12599            window,
12600            cx,
12601        );
12602    });
12603    assert!(!(*is_still_following.borrow()));
12604}
12605
12606#[gpui::test]
12607async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12608    init_test(cx, |_| {});
12609
12610    let fs = FakeFs::new(cx.executor());
12611    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12612    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12613    let pane = workspace
12614        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12615        .unwrap();
12616
12617    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12618
12619    let leader = pane.update_in(cx, |_, window, cx| {
12620        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12621        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12622    });
12623
12624    // Start following the editor when it has no excerpts.
12625    let mut state_message =
12626        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12627    let workspace_entity = workspace.root(cx).unwrap();
12628    let follower_1 = cx
12629        .update_window(*workspace.deref(), |_, window, cx| {
12630            Editor::from_state_proto(
12631                workspace_entity,
12632                ViewId {
12633                    creator: Default::default(),
12634                    id: 0,
12635                },
12636                &mut state_message,
12637                window,
12638                cx,
12639            )
12640        })
12641        .unwrap()
12642        .unwrap()
12643        .await
12644        .unwrap();
12645
12646    let update_message = Rc::new(RefCell::new(None));
12647    follower_1.update_in(cx, {
12648        let update = update_message.clone();
12649        |_, window, cx| {
12650            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12651                leader.read(cx).add_event_to_update_proto(
12652                    event,
12653                    &mut update.borrow_mut(),
12654                    window,
12655                    cx,
12656                );
12657            })
12658            .detach();
12659        }
12660    });
12661
12662    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12663        (
12664            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12665            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12666        )
12667    });
12668
12669    // Insert some excerpts.
12670    leader.update(cx, |leader, cx| {
12671        leader.buffer.update(cx, |multibuffer, cx| {
12672            let excerpt_ids = multibuffer.push_excerpts(
12673                buffer_1.clone(),
12674                [
12675                    ExcerptRange::new(1..6),
12676                    ExcerptRange::new(12..15),
12677                    ExcerptRange::new(0..3),
12678                ],
12679                cx,
12680            );
12681            multibuffer.insert_excerpts_after(
12682                excerpt_ids[0],
12683                buffer_2.clone(),
12684                [ExcerptRange::new(8..12), ExcerptRange::new(0..6)],
12685                cx,
12686            );
12687        });
12688    });
12689
12690    // Apply the update of adding the excerpts.
12691    follower_1
12692        .update_in(cx, |follower, window, cx| {
12693            follower.apply_update_proto(
12694                &project,
12695                update_message.borrow().clone().unwrap(),
12696                window,
12697                cx,
12698            )
12699        })
12700        .await
12701        .unwrap();
12702    assert_eq!(
12703        follower_1.update(cx, |editor, cx| editor.text(cx)),
12704        leader.update(cx, |editor, cx| editor.text(cx))
12705    );
12706    update_message.borrow_mut().take();
12707
12708    // Start following separately after it already has excerpts.
12709    let mut state_message =
12710        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12711    let workspace_entity = workspace.root(cx).unwrap();
12712    let follower_2 = cx
12713        .update_window(*workspace.deref(), |_, window, cx| {
12714            Editor::from_state_proto(
12715                workspace_entity,
12716                ViewId {
12717                    creator: Default::default(),
12718                    id: 0,
12719                },
12720                &mut state_message,
12721                window,
12722                cx,
12723            )
12724        })
12725        .unwrap()
12726        .unwrap()
12727        .await
12728        .unwrap();
12729    assert_eq!(
12730        follower_2.update(cx, |editor, cx| editor.text(cx)),
12731        leader.update(cx, |editor, cx| editor.text(cx))
12732    );
12733
12734    // Remove some excerpts.
12735    leader.update(cx, |leader, cx| {
12736        leader.buffer.update(cx, |multibuffer, cx| {
12737            let excerpt_ids = multibuffer.excerpt_ids();
12738            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12739            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12740        });
12741    });
12742
12743    // Apply the update of removing the excerpts.
12744    follower_1
12745        .update_in(cx, |follower, window, cx| {
12746            follower.apply_update_proto(
12747                &project,
12748                update_message.borrow().clone().unwrap(),
12749                window,
12750                cx,
12751            )
12752        })
12753        .await
12754        .unwrap();
12755    follower_2
12756        .update_in(cx, |follower, window, cx| {
12757            follower.apply_update_proto(
12758                &project,
12759                update_message.borrow().clone().unwrap(),
12760                window,
12761                cx,
12762            )
12763        })
12764        .await
12765        .unwrap();
12766    update_message.borrow_mut().take();
12767    assert_eq!(
12768        follower_1.update(cx, |editor, cx| editor.text(cx)),
12769        leader.update(cx, |editor, cx| editor.text(cx))
12770    );
12771}
12772
12773#[gpui::test]
12774async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12775    init_test(cx, |_| {});
12776
12777    let mut cx = EditorTestContext::new(cx).await;
12778    let lsp_store =
12779        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12780
12781    cx.set_state(indoc! {"
12782        ˇfn func(abc def: i32) -> u32 {
12783        }
12784    "});
12785
12786    cx.update(|_, cx| {
12787        lsp_store.update(cx, |lsp_store, cx| {
12788            lsp_store
12789                .update_diagnostics(
12790                    LanguageServerId(0),
12791                    lsp::PublishDiagnosticsParams {
12792                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12793                        version: None,
12794                        diagnostics: vec![
12795                            lsp::Diagnostic {
12796                                range: lsp::Range::new(
12797                                    lsp::Position::new(0, 11),
12798                                    lsp::Position::new(0, 12),
12799                                ),
12800                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12801                                ..Default::default()
12802                            },
12803                            lsp::Diagnostic {
12804                                range: lsp::Range::new(
12805                                    lsp::Position::new(0, 12),
12806                                    lsp::Position::new(0, 15),
12807                                ),
12808                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12809                                ..Default::default()
12810                            },
12811                            lsp::Diagnostic {
12812                                range: lsp::Range::new(
12813                                    lsp::Position::new(0, 25),
12814                                    lsp::Position::new(0, 28),
12815                                ),
12816                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12817                                ..Default::default()
12818                            },
12819                        ],
12820                    },
12821                    &[],
12822                    cx,
12823                )
12824                .unwrap()
12825        });
12826    });
12827
12828    executor.run_until_parked();
12829
12830    cx.update_editor(|editor, window, cx| {
12831        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12832    });
12833
12834    cx.assert_editor_state(indoc! {"
12835        fn func(abc def: i32) -> ˇu32 {
12836        }
12837    "});
12838
12839    cx.update_editor(|editor, window, cx| {
12840        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12841    });
12842
12843    cx.assert_editor_state(indoc! {"
12844        fn func(abc ˇdef: i32) -> u32 {
12845        }
12846    "});
12847
12848    cx.update_editor(|editor, window, cx| {
12849        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12850    });
12851
12852    cx.assert_editor_state(indoc! {"
12853        fn func(abcˇ def: i32) -> u32 {
12854        }
12855    "});
12856
12857    cx.update_editor(|editor, window, cx| {
12858        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12859    });
12860
12861    cx.assert_editor_state(indoc! {"
12862        fn func(abc def: i32) -> ˇu32 {
12863        }
12864    "});
12865}
12866
12867#[gpui::test]
12868async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12869    init_test(cx, |_| {});
12870
12871    let mut cx = EditorTestContext::new(cx).await;
12872
12873    cx.set_state(indoc! {"
12874        fn func(abˇc def: i32) -> u32 {
12875        }
12876    "});
12877    let lsp_store =
12878        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12879
12880    cx.update(|_, cx| {
12881        lsp_store.update(cx, |lsp_store, cx| {
12882            lsp_store.update_diagnostics(
12883                LanguageServerId(0),
12884                lsp::PublishDiagnosticsParams {
12885                    uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12886                    version: None,
12887                    diagnostics: vec![lsp::Diagnostic {
12888                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12889                        severity: Some(lsp::DiagnosticSeverity::ERROR),
12890                        message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12891                        ..Default::default()
12892                    }],
12893                },
12894                &[],
12895                cx,
12896            )
12897        })
12898    }).unwrap();
12899    cx.run_until_parked();
12900    cx.update_editor(|editor, window, cx| {
12901        hover_popover::hover(editor, &Default::default(), window, cx)
12902    });
12903    cx.run_until_parked();
12904    cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12905}
12906
12907#[gpui::test]
12908async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12909    init_test(cx, |_| {});
12910
12911    let mut cx = EditorTestContext::new(cx).await;
12912
12913    let diff_base = r#"
12914        use some::mod;
12915
12916        const A: u32 = 42;
12917
12918        fn main() {
12919            println!("hello");
12920
12921            println!("world");
12922        }
12923        "#
12924    .unindent();
12925
12926    // Edits are modified, removed, modified, added
12927    cx.set_state(
12928        &r#"
12929        use some::modified;
12930
12931        ˇ
12932        fn main() {
12933            println!("hello there");
12934
12935            println!("around the");
12936            println!("world");
12937        }
12938        "#
12939        .unindent(),
12940    );
12941
12942    cx.set_head_text(&diff_base);
12943    executor.run_until_parked();
12944
12945    cx.update_editor(|editor, window, cx| {
12946        //Wrap around the bottom of the buffer
12947        for _ in 0..3 {
12948            editor.go_to_next_hunk(&GoToHunk, window, cx);
12949        }
12950    });
12951
12952    cx.assert_editor_state(
12953        &r#"
12954        ˇuse some::modified;
12955
12956
12957        fn main() {
12958            println!("hello there");
12959
12960            println!("around the");
12961            println!("world");
12962        }
12963        "#
12964        .unindent(),
12965    );
12966
12967    cx.update_editor(|editor, window, cx| {
12968        //Wrap around the top of the buffer
12969        for _ in 0..2 {
12970            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12971        }
12972    });
12973
12974    cx.assert_editor_state(
12975        &r#"
12976        use some::modified;
12977
12978
12979        fn main() {
12980        ˇ    println!("hello there");
12981
12982            println!("around the");
12983            println!("world");
12984        }
12985        "#
12986        .unindent(),
12987    );
12988
12989    cx.update_editor(|editor, window, cx| {
12990        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12991    });
12992
12993    cx.assert_editor_state(
12994        &r#"
12995        use some::modified;
12996
12997        ˇ
12998        fn main() {
12999            println!("hello there");
13000
13001            println!("around the");
13002            println!("world");
13003        }
13004        "#
13005        .unindent(),
13006    );
13007
13008    cx.update_editor(|editor, window, cx| {
13009        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13010    });
13011
13012    cx.assert_editor_state(
13013        &r#"
13014        ˇuse some::modified;
13015
13016
13017        fn main() {
13018            println!("hello there");
13019
13020            println!("around the");
13021            println!("world");
13022        }
13023        "#
13024        .unindent(),
13025    );
13026
13027    cx.update_editor(|editor, window, cx| {
13028        for _ in 0..2 {
13029            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13030        }
13031    });
13032
13033    cx.assert_editor_state(
13034        &r#"
13035        use some::modified;
13036
13037
13038        fn main() {
13039        ˇ    println!("hello there");
13040
13041            println!("around the");
13042            println!("world");
13043        }
13044        "#
13045        .unindent(),
13046    );
13047
13048    cx.update_editor(|editor, window, cx| {
13049        editor.fold(&Fold, window, cx);
13050    });
13051
13052    cx.update_editor(|editor, window, cx| {
13053        editor.go_to_next_hunk(&GoToHunk, window, cx);
13054    });
13055
13056    cx.assert_editor_state(
13057        &r#"
13058        ˇuse some::modified;
13059
13060
13061        fn main() {
13062            println!("hello there");
13063
13064            println!("around the");
13065            println!("world");
13066        }
13067        "#
13068        .unindent(),
13069    );
13070}
13071
13072#[test]
13073fn test_split_words() {
13074    fn split(text: &str) -> Vec<&str> {
13075        split_words(text).collect()
13076    }
13077
13078    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13079    assert_eq!(split("hello_world"), &["hello_", "world"]);
13080    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13081    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13082    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13083    assert_eq!(split("helloworld"), &["helloworld"]);
13084
13085    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13086}
13087
13088#[gpui::test]
13089async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13090    init_test(cx, |_| {});
13091
13092    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13093    let mut assert = |before, after| {
13094        let _state_context = cx.set_state(before);
13095        cx.run_until_parked();
13096        cx.update_editor(|editor, window, cx| {
13097            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13098        });
13099        cx.run_until_parked();
13100        cx.assert_editor_state(after);
13101    };
13102
13103    // Outside bracket jumps to outside of matching bracket
13104    assert("console.logˇ(var);", "console.log(var)ˇ;");
13105    assert("console.log(var)ˇ;", "console.logˇ(var);");
13106
13107    // Inside bracket jumps to inside of matching bracket
13108    assert("console.log(ˇvar);", "console.log(varˇ);");
13109    assert("console.log(varˇ);", "console.log(ˇvar);");
13110
13111    // When outside a bracket and inside, favor jumping to the inside bracket
13112    assert(
13113        "console.log('foo', [1, 2, 3]ˇ);",
13114        "console.log(ˇ'foo', [1, 2, 3]);",
13115    );
13116    assert(
13117        "console.log(ˇ'foo', [1, 2, 3]);",
13118        "console.log('foo', [1, 2, 3]ˇ);",
13119    );
13120
13121    // Bias forward if two options are equally likely
13122    assert(
13123        "let result = curried_fun()ˇ();",
13124        "let result = curried_fun()()ˇ;",
13125    );
13126
13127    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13128    assert(
13129        indoc! {"
13130            function test() {
13131                console.log('test')ˇ
13132            }"},
13133        indoc! {"
13134            function test() {
13135                console.logˇ('test')
13136            }"},
13137    );
13138}
13139
13140#[gpui::test]
13141async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13142    init_test(cx, |_| {});
13143
13144    let fs = FakeFs::new(cx.executor());
13145    fs.insert_tree(
13146        path!("/a"),
13147        json!({
13148            "main.rs": "fn main() { let a = 5; }",
13149            "other.rs": "// Test file",
13150        }),
13151    )
13152    .await;
13153    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13154
13155    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13156    language_registry.add(Arc::new(Language::new(
13157        LanguageConfig {
13158            name: "Rust".into(),
13159            matcher: LanguageMatcher {
13160                path_suffixes: vec!["rs".to_string()],
13161                ..Default::default()
13162            },
13163            brackets: BracketPairConfig {
13164                pairs: vec![BracketPair {
13165                    start: "{".to_string(),
13166                    end: "}".to_string(),
13167                    close: true,
13168                    surround: true,
13169                    newline: true,
13170                }],
13171                disabled_scopes_by_bracket_ix: Vec::new(),
13172            },
13173            ..Default::default()
13174        },
13175        Some(tree_sitter_rust::LANGUAGE.into()),
13176    )));
13177    let mut fake_servers = language_registry.register_fake_lsp(
13178        "Rust",
13179        FakeLspAdapter {
13180            capabilities: lsp::ServerCapabilities {
13181                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13182                    first_trigger_character: "{".to_string(),
13183                    more_trigger_character: None,
13184                }),
13185                ..Default::default()
13186            },
13187            ..Default::default()
13188        },
13189    );
13190
13191    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13192
13193    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13194
13195    let worktree_id = workspace
13196        .update(cx, |workspace, _, cx| {
13197            workspace.project().update(cx, |project, cx| {
13198                project.worktrees(cx).next().unwrap().read(cx).id()
13199            })
13200        })
13201        .unwrap();
13202
13203    let buffer = project
13204        .update(cx, |project, cx| {
13205            project.open_local_buffer(path!("/a/main.rs"), cx)
13206        })
13207        .await
13208        .unwrap();
13209    let editor_handle = workspace
13210        .update(cx, |workspace, window, cx| {
13211            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13212        })
13213        .unwrap()
13214        .await
13215        .unwrap()
13216        .downcast::<Editor>()
13217        .unwrap();
13218
13219    cx.executor().start_waiting();
13220    let fake_server = fake_servers.next().await.unwrap();
13221
13222    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13223        |params, _| async move {
13224            assert_eq!(
13225                params.text_document_position.text_document.uri,
13226                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13227            );
13228            assert_eq!(
13229                params.text_document_position.position,
13230                lsp::Position::new(0, 21),
13231            );
13232
13233            Ok(Some(vec![lsp::TextEdit {
13234                new_text: "]".to_string(),
13235                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13236            }]))
13237        },
13238    );
13239
13240    editor_handle.update_in(cx, |editor, window, cx| {
13241        window.focus(&editor.focus_handle(cx));
13242        editor.change_selections(None, window, cx, |s| {
13243            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13244        });
13245        editor.handle_input("{", window, cx);
13246    });
13247
13248    cx.executor().run_until_parked();
13249
13250    buffer.update(cx, |buffer, _| {
13251        assert_eq!(
13252            buffer.text(),
13253            "fn main() { let a = {5}; }",
13254            "No extra braces from on type formatting should appear in the buffer"
13255        )
13256    });
13257}
13258
13259#[gpui::test]
13260async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13261    init_test(cx, |_| {});
13262
13263    let fs = FakeFs::new(cx.executor());
13264    fs.insert_tree(
13265        path!("/a"),
13266        json!({
13267            "main.rs": "fn main() { let a = 5; }",
13268            "other.rs": "// Test file",
13269        }),
13270    )
13271    .await;
13272
13273    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13274
13275    let server_restarts = Arc::new(AtomicUsize::new(0));
13276    let closure_restarts = Arc::clone(&server_restarts);
13277    let language_server_name = "test language server";
13278    let language_name: LanguageName = "Rust".into();
13279
13280    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13281    language_registry.add(Arc::new(Language::new(
13282        LanguageConfig {
13283            name: language_name.clone(),
13284            matcher: LanguageMatcher {
13285                path_suffixes: vec!["rs".to_string()],
13286                ..Default::default()
13287            },
13288            ..Default::default()
13289        },
13290        Some(tree_sitter_rust::LANGUAGE.into()),
13291    )));
13292    let mut fake_servers = language_registry.register_fake_lsp(
13293        "Rust",
13294        FakeLspAdapter {
13295            name: language_server_name,
13296            initialization_options: Some(json!({
13297                "testOptionValue": true
13298            })),
13299            initializer: Some(Box::new(move |fake_server| {
13300                let task_restarts = Arc::clone(&closure_restarts);
13301                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13302                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13303                    futures::future::ready(Ok(()))
13304                });
13305            })),
13306            ..Default::default()
13307        },
13308    );
13309
13310    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13311    let _buffer = project
13312        .update(cx, |project, cx| {
13313            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13314        })
13315        .await
13316        .unwrap();
13317    let _fake_server = fake_servers.next().await.unwrap();
13318    update_test_language_settings(cx, |language_settings| {
13319        language_settings.languages.insert(
13320            language_name.clone(),
13321            LanguageSettingsContent {
13322                tab_size: NonZeroU32::new(8),
13323                ..Default::default()
13324            },
13325        );
13326    });
13327    cx.executor().run_until_parked();
13328    assert_eq!(
13329        server_restarts.load(atomic::Ordering::Acquire),
13330        0,
13331        "Should not restart LSP server on an unrelated change"
13332    );
13333
13334    update_test_project_settings(cx, |project_settings| {
13335        project_settings.lsp.insert(
13336            "Some other server name".into(),
13337            LspSettings {
13338                binary: None,
13339                settings: None,
13340                initialization_options: Some(json!({
13341                    "some other init value": false
13342                })),
13343                enable_lsp_tasks: false,
13344            },
13345        );
13346    });
13347    cx.executor().run_until_parked();
13348    assert_eq!(
13349        server_restarts.load(atomic::Ordering::Acquire),
13350        0,
13351        "Should not restart LSP server on an unrelated LSP settings change"
13352    );
13353
13354    update_test_project_settings(cx, |project_settings| {
13355        project_settings.lsp.insert(
13356            language_server_name.into(),
13357            LspSettings {
13358                binary: None,
13359                settings: None,
13360                initialization_options: Some(json!({
13361                    "anotherInitValue": false
13362                })),
13363                enable_lsp_tasks: false,
13364            },
13365        );
13366    });
13367    cx.executor().run_until_parked();
13368    assert_eq!(
13369        server_restarts.load(atomic::Ordering::Acquire),
13370        1,
13371        "Should restart LSP server on a related LSP settings change"
13372    );
13373
13374    update_test_project_settings(cx, |project_settings| {
13375        project_settings.lsp.insert(
13376            language_server_name.into(),
13377            LspSettings {
13378                binary: None,
13379                settings: None,
13380                initialization_options: Some(json!({
13381                    "anotherInitValue": false
13382                })),
13383                enable_lsp_tasks: false,
13384            },
13385        );
13386    });
13387    cx.executor().run_until_parked();
13388    assert_eq!(
13389        server_restarts.load(atomic::Ordering::Acquire),
13390        1,
13391        "Should not restart LSP server on a related LSP settings change that is the same"
13392    );
13393
13394    update_test_project_settings(cx, |project_settings| {
13395        project_settings.lsp.insert(
13396            language_server_name.into(),
13397            LspSettings {
13398                binary: None,
13399                settings: None,
13400                initialization_options: None,
13401                enable_lsp_tasks: false,
13402            },
13403        );
13404    });
13405    cx.executor().run_until_parked();
13406    assert_eq!(
13407        server_restarts.load(atomic::Ordering::Acquire),
13408        2,
13409        "Should restart LSP server on another related LSP settings change"
13410    );
13411}
13412
13413#[gpui::test]
13414async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13415    init_test(cx, |_| {});
13416
13417    let mut cx = EditorLspTestContext::new_rust(
13418        lsp::ServerCapabilities {
13419            completion_provider: Some(lsp::CompletionOptions {
13420                trigger_characters: Some(vec![".".to_string()]),
13421                resolve_provider: Some(true),
13422                ..Default::default()
13423            }),
13424            ..Default::default()
13425        },
13426        cx,
13427    )
13428    .await;
13429
13430    cx.set_state("fn main() { let a = 2ˇ; }");
13431    cx.simulate_keystroke(".");
13432    let completion_item = lsp::CompletionItem {
13433        label: "some".into(),
13434        kind: Some(lsp::CompletionItemKind::SNIPPET),
13435        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13436        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13437            kind: lsp::MarkupKind::Markdown,
13438            value: "```rust\nSome(2)\n```".to_string(),
13439        })),
13440        deprecated: Some(false),
13441        sort_text: Some("fffffff2".to_string()),
13442        filter_text: Some("some".to_string()),
13443        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13444        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13445            range: lsp::Range {
13446                start: lsp::Position {
13447                    line: 0,
13448                    character: 22,
13449                },
13450                end: lsp::Position {
13451                    line: 0,
13452                    character: 22,
13453                },
13454            },
13455            new_text: "Some(2)".to_string(),
13456        })),
13457        additional_text_edits: Some(vec![lsp::TextEdit {
13458            range: lsp::Range {
13459                start: lsp::Position {
13460                    line: 0,
13461                    character: 20,
13462                },
13463                end: lsp::Position {
13464                    line: 0,
13465                    character: 22,
13466                },
13467            },
13468            new_text: "".to_string(),
13469        }]),
13470        ..Default::default()
13471    };
13472
13473    let closure_completion_item = completion_item.clone();
13474    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13475        let task_completion_item = closure_completion_item.clone();
13476        async move {
13477            Ok(Some(lsp::CompletionResponse::Array(vec![
13478                task_completion_item,
13479            ])))
13480        }
13481    });
13482
13483    request.next().await;
13484
13485    cx.condition(|editor, _| editor.context_menu_visible())
13486        .await;
13487    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13488        editor
13489            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13490            .unwrap()
13491    });
13492    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13493
13494    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13495        let task_completion_item = completion_item.clone();
13496        async move { Ok(task_completion_item) }
13497    })
13498    .next()
13499    .await
13500    .unwrap();
13501    apply_additional_edits.await.unwrap();
13502    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13503}
13504
13505#[gpui::test]
13506async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13507    init_test(cx, |_| {});
13508
13509    let mut cx = EditorLspTestContext::new_rust(
13510        lsp::ServerCapabilities {
13511            completion_provider: Some(lsp::CompletionOptions {
13512                trigger_characters: Some(vec![".".to_string()]),
13513                resolve_provider: Some(true),
13514                ..Default::default()
13515            }),
13516            ..Default::default()
13517        },
13518        cx,
13519    )
13520    .await;
13521
13522    cx.set_state("fn main() { let a = 2ˇ; }");
13523    cx.simulate_keystroke(".");
13524
13525    let item1 = lsp::CompletionItem {
13526        label: "method id()".to_string(),
13527        filter_text: Some("id".to_string()),
13528        detail: None,
13529        documentation: None,
13530        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13531            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13532            new_text: ".id".to_string(),
13533        })),
13534        ..lsp::CompletionItem::default()
13535    };
13536
13537    let item2 = lsp::CompletionItem {
13538        label: "other".to_string(),
13539        filter_text: Some("other".to_string()),
13540        detail: None,
13541        documentation: None,
13542        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13543            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13544            new_text: ".other".to_string(),
13545        })),
13546        ..lsp::CompletionItem::default()
13547    };
13548
13549    let item1 = item1.clone();
13550    cx.set_request_handler::<lsp::request::Completion, _, _>({
13551        let item1 = item1.clone();
13552        move |_, _, _| {
13553            let item1 = item1.clone();
13554            let item2 = item2.clone();
13555            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13556        }
13557    })
13558    .next()
13559    .await;
13560
13561    cx.condition(|editor, _| editor.context_menu_visible())
13562        .await;
13563    cx.update_editor(|editor, _, _| {
13564        let context_menu = editor.context_menu.borrow_mut();
13565        let context_menu = context_menu
13566            .as_ref()
13567            .expect("Should have the context menu deployed");
13568        match context_menu {
13569            CodeContextMenu::Completions(completions_menu) => {
13570                let completions = completions_menu.completions.borrow_mut();
13571                assert_eq!(
13572                    completions
13573                        .iter()
13574                        .map(|completion| &completion.label.text)
13575                        .collect::<Vec<_>>(),
13576                    vec!["method id()", "other"]
13577                )
13578            }
13579            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13580        }
13581    });
13582
13583    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13584        let item1 = item1.clone();
13585        move |_, item_to_resolve, _| {
13586            let item1 = item1.clone();
13587            async move {
13588                if item1 == item_to_resolve {
13589                    Ok(lsp::CompletionItem {
13590                        label: "method id()".to_string(),
13591                        filter_text: Some("id".to_string()),
13592                        detail: Some("Now resolved!".to_string()),
13593                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13594                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13595                            range: lsp::Range::new(
13596                                lsp::Position::new(0, 22),
13597                                lsp::Position::new(0, 22),
13598                            ),
13599                            new_text: ".id".to_string(),
13600                        })),
13601                        ..lsp::CompletionItem::default()
13602                    })
13603                } else {
13604                    Ok(item_to_resolve)
13605                }
13606            }
13607        }
13608    })
13609    .next()
13610    .await
13611    .unwrap();
13612    cx.run_until_parked();
13613
13614    cx.update_editor(|editor, window, cx| {
13615        editor.context_menu_next(&Default::default(), window, cx);
13616    });
13617
13618    cx.update_editor(|editor, _, _| {
13619        let context_menu = editor.context_menu.borrow_mut();
13620        let context_menu = context_menu
13621            .as_ref()
13622            .expect("Should have the context menu deployed");
13623        match context_menu {
13624            CodeContextMenu::Completions(completions_menu) => {
13625                let completions = completions_menu.completions.borrow_mut();
13626                assert_eq!(
13627                    completions
13628                        .iter()
13629                        .map(|completion| &completion.label.text)
13630                        .collect::<Vec<_>>(),
13631                    vec!["method id() Now resolved!", "other"],
13632                    "Should update first completion label, but not second as the filter text did not match."
13633                );
13634            }
13635            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13636        }
13637    });
13638}
13639
13640#[gpui::test]
13641async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13642    init_test(cx, |_| {});
13643
13644    let mut cx = EditorLspTestContext::new_rust(
13645        lsp::ServerCapabilities {
13646            completion_provider: Some(lsp::CompletionOptions {
13647                trigger_characters: Some(vec![".".to_string()]),
13648                resolve_provider: Some(true),
13649                ..Default::default()
13650            }),
13651            ..Default::default()
13652        },
13653        cx,
13654    )
13655    .await;
13656
13657    cx.set_state("fn main() { let a = 2ˇ; }");
13658    cx.simulate_keystroke(".");
13659
13660    let unresolved_item_1 = lsp::CompletionItem {
13661        label: "id".to_string(),
13662        filter_text: Some("id".to_string()),
13663        detail: None,
13664        documentation: None,
13665        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13666            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13667            new_text: ".id".to_string(),
13668        })),
13669        ..lsp::CompletionItem::default()
13670    };
13671    let resolved_item_1 = lsp::CompletionItem {
13672        additional_text_edits: Some(vec![lsp::TextEdit {
13673            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13674            new_text: "!!".to_string(),
13675        }]),
13676        ..unresolved_item_1.clone()
13677    };
13678    let unresolved_item_2 = lsp::CompletionItem {
13679        label: "other".to_string(),
13680        filter_text: Some("other".to_string()),
13681        detail: None,
13682        documentation: None,
13683        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13684            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13685            new_text: ".other".to_string(),
13686        })),
13687        ..lsp::CompletionItem::default()
13688    };
13689    let resolved_item_2 = lsp::CompletionItem {
13690        additional_text_edits: Some(vec![lsp::TextEdit {
13691            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13692            new_text: "??".to_string(),
13693        }]),
13694        ..unresolved_item_2.clone()
13695    };
13696
13697    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13698    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13699    cx.lsp
13700        .server
13701        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13702            let unresolved_item_1 = unresolved_item_1.clone();
13703            let resolved_item_1 = resolved_item_1.clone();
13704            let unresolved_item_2 = unresolved_item_2.clone();
13705            let resolved_item_2 = resolved_item_2.clone();
13706            let resolve_requests_1 = resolve_requests_1.clone();
13707            let resolve_requests_2 = resolve_requests_2.clone();
13708            move |unresolved_request, _| {
13709                let unresolved_item_1 = unresolved_item_1.clone();
13710                let resolved_item_1 = resolved_item_1.clone();
13711                let unresolved_item_2 = unresolved_item_2.clone();
13712                let resolved_item_2 = resolved_item_2.clone();
13713                let resolve_requests_1 = resolve_requests_1.clone();
13714                let resolve_requests_2 = resolve_requests_2.clone();
13715                async move {
13716                    if unresolved_request == unresolved_item_1 {
13717                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13718                        Ok(resolved_item_1.clone())
13719                    } else if unresolved_request == unresolved_item_2 {
13720                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13721                        Ok(resolved_item_2.clone())
13722                    } else {
13723                        panic!("Unexpected completion item {unresolved_request:?}")
13724                    }
13725                }
13726            }
13727        })
13728        .detach();
13729
13730    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13731        let unresolved_item_1 = unresolved_item_1.clone();
13732        let unresolved_item_2 = unresolved_item_2.clone();
13733        async move {
13734            Ok(Some(lsp::CompletionResponse::Array(vec![
13735                unresolved_item_1,
13736                unresolved_item_2,
13737            ])))
13738        }
13739    })
13740    .next()
13741    .await;
13742
13743    cx.condition(|editor, _| editor.context_menu_visible())
13744        .await;
13745    cx.update_editor(|editor, _, _| {
13746        let context_menu = editor.context_menu.borrow_mut();
13747        let context_menu = context_menu
13748            .as_ref()
13749            .expect("Should have the context menu deployed");
13750        match context_menu {
13751            CodeContextMenu::Completions(completions_menu) => {
13752                let completions = completions_menu.completions.borrow_mut();
13753                assert_eq!(
13754                    completions
13755                        .iter()
13756                        .map(|completion| &completion.label.text)
13757                        .collect::<Vec<_>>(),
13758                    vec!["id", "other"]
13759                )
13760            }
13761            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13762        }
13763    });
13764    cx.run_until_parked();
13765
13766    cx.update_editor(|editor, window, cx| {
13767        editor.context_menu_next(&ContextMenuNext, window, cx);
13768    });
13769    cx.run_until_parked();
13770    cx.update_editor(|editor, window, cx| {
13771        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13772    });
13773    cx.run_until_parked();
13774    cx.update_editor(|editor, window, cx| {
13775        editor.context_menu_next(&ContextMenuNext, window, cx);
13776    });
13777    cx.run_until_parked();
13778    cx.update_editor(|editor, window, cx| {
13779        editor
13780            .compose_completion(&ComposeCompletion::default(), window, cx)
13781            .expect("No task returned")
13782    })
13783    .await
13784    .expect("Completion failed");
13785    cx.run_until_parked();
13786
13787    cx.update_editor(|editor, _, cx| {
13788        assert_eq!(
13789            resolve_requests_1.load(atomic::Ordering::Acquire),
13790            1,
13791            "Should always resolve once despite multiple selections"
13792        );
13793        assert_eq!(
13794            resolve_requests_2.load(atomic::Ordering::Acquire),
13795            1,
13796            "Should always resolve once after multiple selections and applying the completion"
13797        );
13798        assert_eq!(
13799            editor.text(cx),
13800            "fn main() { let a = ??.other; }",
13801            "Should use resolved data when applying the completion"
13802        );
13803    });
13804}
13805
13806#[gpui::test]
13807async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13808    init_test(cx, |_| {});
13809
13810    let item_0 = lsp::CompletionItem {
13811        label: "abs".into(),
13812        insert_text: Some("abs".into()),
13813        data: Some(json!({ "very": "special"})),
13814        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13815        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13816            lsp::InsertReplaceEdit {
13817                new_text: "abs".to_string(),
13818                insert: lsp::Range::default(),
13819                replace: lsp::Range::default(),
13820            },
13821        )),
13822        ..lsp::CompletionItem::default()
13823    };
13824    let items = iter::once(item_0.clone())
13825        .chain((11..51).map(|i| lsp::CompletionItem {
13826            label: format!("item_{}", i),
13827            insert_text: Some(format!("item_{}", i)),
13828            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13829            ..lsp::CompletionItem::default()
13830        }))
13831        .collect::<Vec<_>>();
13832
13833    let default_commit_characters = vec!["?".to_string()];
13834    let default_data = json!({ "default": "data"});
13835    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13836    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13837    let default_edit_range = lsp::Range {
13838        start: lsp::Position {
13839            line: 0,
13840            character: 5,
13841        },
13842        end: lsp::Position {
13843            line: 0,
13844            character: 5,
13845        },
13846    };
13847
13848    let mut cx = EditorLspTestContext::new_rust(
13849        lsp::ServerCapabilities {
13850            completion_provider: Some(lsp::CompletionOptions {
13851                trigger_characters: Some(vec![".".to_string()]),
13852                resolve_provider: Some(true),
13853                ..Default::default()
13854            }),
13855            ..Default::default()
13856        },
13857        cx,
13858    )
13859    .await;
13860
13861    cx.set_state("fn main() { let a = 2ˇ; }");
13862    cx.simulate_keystroke(".");
13863
13864    let completion_data = default_data.clone();
13865    let completion_characters = default_commit_characters.clone();
13866    let completion_items = items.clone();
13867    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13868        let default_data = completion_data.clone();
13869        let default_commit_characters = completion_characters.clone();
13870        let items = completion_items.clone();
13871        async move {
13872            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13873                items,
13874                item_defaults: Some(lsp::CompletionListItemDefaults {
13875                    data: Some(default_data.clone()),
13876                    commit_characters: Some(default_commit_characters.clone()),
13877                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13878                        default_edit_range,
13879                    )),
13880                    insert_text_format: Some(default_insert_text_format),
13881                    insert_text_mode: Some(default_insert_text_mode),
13882                }),
13883                ..lsp::CompletionList::default()
13884            })))
13885        }
13886    })
13887    .next()
13888    .await;
13889
13890    let resolved_items = Arc::new(Mutex::new(Vec::new()));
13891    cx.lsp
13892        .server
13893        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13894            let closure_resolved_items = resolved_items.clone();
13895            move |item_to_resolve, _| {
13896                let closure_resolved_items = closure_resolved_items.clone();
13897                async move {
13898                    closure_resolved_items.lock().push(item_to_resolve.clone());
13899                    Ok(item_to_resolve)
13900                }
13901            }
13902        })
13903        .detach();
13904
13905    cx.condition(|editor, _| editor.context_menu_visible())
13906        .await;
13907    cx.run_until_parked();
13908    cx.update_editor(|editor, _, _| {
13909        let menu = editor.context_menu.borrow_mut();
13910        match menu.as_ref().expect("should have the completions menu") {
13911            CodeContextMenu::Completions(completions_menu) => {
13912                assert_eq!(
13913                    completions_menu
13914                        .entries
13915                        .borrow()
13916                        .iter()
13917                        .map(|mat| mat.string.clone())
13918                        .collect::<Vec<String>>(),
13919                    items
13920                        .iter()
13921                        .map(|completion| completion.label.clone())
13922                        .collect::<Vec<String>>()
13923                );
13924            }
13925            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13926        }
13927    });
13928    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13929    // with 4 from the end.
13930    assert_eq!(
13931        *resolved_items.lock(),
13932        [&items[0..16], &items[items.len() - 4..items.len()]]
13933            .concat()
13934            .iter()
13935            .cloned()
13936            .map(|mut item| {
13937                if item.data.is_none() {
13938                    item.data = Some(default_data.clone());
13939                }
13940                item
13941            })
13942            .collect::<Vec<lsp::CompletionItem>>(),
13943        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13944    );
13945    resolved_items.lock().clear();
13946
13947    cx.update_editor(|editor, window, cx| {
13948        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13949    });
13950    cx.run_until_parked();
13951    // Completions that have already been resolved are skipped.
13952    assert_eq!(
13953        *resolved_items.lock(),
13954        items[items.len() - 16..items.len() - 4]
13955            .iter()
13956            .cloned()
13957            .map(|mut item| {
13958                if item.data.is_none() {
13959                    item.data = Some(default_data.clone());
13960                }
13961                item
13962            })
13963            .collect::<Vec<lsp::CompletionItem>>()
13964    );
13965    resolved_items.lock().clear();
13966}
13967
13968#[gpui::test]
13969async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13970    init_test(cx, |_| {});
13971
13972    let mut cx = EditorLspTestContext::new(
13973        Language::new(
13974            LanguageConfig {
13975                matcher: LanguageMatcher {
13976                    path_suffixes: vec!["jsx".into()],
13977                    ..Default::default()
13978                },
13979                overrides: [(
13980                    "element".into(),
13981                    LanguageConfigOverride {
13982                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
13983                        ..Default::default()
13984                    },
13985                )]
13986                .into_iter()
13987                .collect(),
13988                ..Default::default()
13989            },
13990            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13991        )
13992        .with_override_query("(jsx_self_closing_element) @element")
13993        .unwrap(),
13994        lsp::ServerCapabilities {
13995            completion_provider: Some(lsp::CompletionOptions {
13996                trigger_characters: Some(vec![":".to_string()]),
13997                ..Default::default()
13998            }),
13999            ..Default::default()
14000        },
14001        cx,
14002    )
14003    .await;
14004
14005    cx.lsp
14006        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14007            Ok(Some(lsp::CompletionResponse::Array(vec![
14008                lsp::CompletionItem {
14009                    label: "bg-blue".into(),
14010                    ..Default::default()
14011                },
14012                lsp::CompletionItem {
14013                    label: "bg-red".into(),
14014                    ..Default::default()
14015                },
14016                lsp::CompletionItem {
14017                    label: "bg-yellow".into(),
14018                    ..Default::default()
14019                },
14020            ])))
14021        });
14022
14023    cx.set_state(r#"<p class="bgˇ" />"#);
14024
14025    // Trigger completion when typing a dash, because the dash is an extra
14026    // word character in the 'element' scope, which contains the cursor.
14027    cx.simulate_keystroke("-");
14028    cx.executor().run_until_parked();
14029    cx.update_editor(|editor, _, _| {
14030        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14031        {
14032            assert_eq!(
14033                completion_menu_entries(&menu),
14034                &["bg-red", "bg-blue", "bg-yellow"]
14035            );
14036        } else {
14037            panic!("expected completion menu to be open");
14038        }
14039    });
14040
14041    cx.simulate_keystroke("l");
14042    cx.executor().run_until_parked();
14043    cx.update_editor(|editor, _, _| {
14044        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14045        {
14046            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14047        } else {
14048            panic!("expected completion menu to be open");
14049        }
14050    });
14051
14052    // When filtering completions, consider the character after the '-' to
14053    // be the start of a subword.
14054    cx.set_state(r#"<p class="yelˇ" />"#);
14055    cx.simulate_keystroke("l");
14056    cx.executor().run_until_parked();
14057    cx.update_editor(|editor, _, _| {
14058        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14059        {
14060            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14061        } else {
14062            panic!("expected completion menu to be open");
14063        }
14064    });
14065}
14066
14067fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14068    let entries = menu.entries.borrow();
14069    entries.iter().map(|mat| mat.string.clone()).collect()
14070}
14071
14072#[gpui::test]
14073async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14074    init_test(cx, |settings| {
14075        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14076            FormatterList(vec![Formatter::Prettier].into()),
14077        ))
14078    });
14079
14080    let fs = FakeFs::new(cx.executor());
14081    fs.insert_file(path!("/file.ts"), Default::default()).await;
14082
14083    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14085
14086    language_registry.add(Arc::new(Language::new(
14087        LanguageConfig {
14088            name: "TypeScript".into(),
14089            matcher: LanguageMatcher {
14090                path_suffixes: vec!["ts".to_string()],
14091                ..Default::default()
14092            },
14093            ..Default::default()
14094        },
14095        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14096    )));
14097    update_test_language_settings(cx, |settings| {
14098        settings.defaults.prettier = Some(PrettierSettings {
14099            allowed: true,
14100            ..PrettierSettings::default()
14101        });
14102    });
14103
14104    let test_plugin = "test_plugin";
14105    let _ = language_registry.register_fake_lsp(
14106        "TypeScript",
14107        FakeLspAdapter {
14108            prettier_plugins: vec![test_plugin],
14109            ..Default::default()
14110        },
14111    );
14112
14113    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14114    let buffer = project
14115        .update(cx, |project, cx| {
14116            project.open_local_buffer(path!("/file.ts"), cx)
14117        })
14118        .await
14119        .unwrap();
14120
14121    let buffer_text = "one\ntwo\nthree\n";
14122    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14123    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14124    editor.update_in(cx, |editor, window, cx| {
14125        editor.set_text(buffer_text, window, cx)
14126    });
14127
14128    editor
14129        .update_in(cx, |editor, window, cx| {
14130            editor.perform_format(
14131                project.clone(),
14132                FormatTrigger::Manual,
14133                FormatTarget::Buffers,
14134                window,
14135                cx,
14136            )
14137        })
14138        .unwrap()
14139        .await;
14140    assert_eq!(
14141        editor.update(cx, |editor, cx| editor.text(cx)),
14142        buffer_text.to_string() + prettier_format_suffix,
14143        "Test prettier formatting was not applied to the original buffer text",
14144    );
14145
14146    update_test_language_settings(cx, |settings| {
14147        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14148    });
14149    let format = editor.update_in(cx, |editor, window, cx| {
14150        editor.perform_format(
14151            project.clone(),
14152            FormatTrigger::Manual,
14153            FormatTarget::Buffers,
14154            window,
14155            cx,
14156        )
14157    });
14158    format.await.unwrap();
14159    assert_eq!(
14160        editor.update(cx, |editor, cx| editor.text(cx)),
14161        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14162        "Autoformatting (via test prettier) was not applied to the original buffer text",
14163    );
14164}
14165
14166#[gpui::test]
14167async fn test_addition_reverts(cx: &mut TestAppContext) {
14168    init_test(cx, |_| {});
14169    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14170    let base_text = indoc! {r#"
14171        struct Row;
14172        struct Row1;
14173        struct Row2;
14174
14175        struct Row4;
14176        struct Row5;
14177        struct Row6;
14178
14179        struct Row8;
14180        struct Row9;
14181        struct Row10;"#};
14182
14183    // When addition hunks are not adjacent to carets, no hunk revert is performed
14184    assert_hunk_revert(
14185        indoc! {r#"struct Row;
14186                   struct Row1;
14187                   struct Row1.1;
14188                   struct Row1.2;
14189                   struct Row2;ˇ
14190
14191                   struct Row4;
14192                   struct Row5;
14193                   struct Row6;
14194
14195                   struct Row8;
14196                   ˇstruct Row9;
14197                   struct Row9.1;
14198                   struct Row9.2;
14199                   struct Row9.3;
14200                   struct Row10;"#},
14201        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14202        indoc! {r#"struct Row;
14203                   struct Row1;
14204                   struct Row1.1;
14205                   struct Row1.2;
14206                   struct Row2;ˇ
14207
14208                   struct Row4;
14209                   struct Row5;
14210                   struct Row6;
14211
14212                   struct Row8;
14213                   ˇstruct Row9;
14214                   struct Row9.1;
14215                   struct Row9.2;
14216                   struct Row9.3;
14217                   struct Row10;"#},
14218        base_text,
14219        &mut cx,
14220    );
14221    // Same for selections
14222    assert_hunk_revert(
14223        indoc! {r#"struct Row;
14224                   struct Row1;
14225                   struct Row2;
14226                   struct Row2.1;
14227                   struct Row2.2;
14228                   «ˇ
14229                   struct Row4;
14230                   struct» Row5;
14231                   «struct Row6;
14232                   ˇ»
14233                   struct Row9.1;
14234                   struct Row9.2;
14235                   struct Row9.3;
14236                   struct Row8;
14237                   struct Row9;
14238                   struct Row10;"#},
14239        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14240        indoc! {r#"struct Row;
14241                   struct Row1;
14242                   struct Row2;
14243                   struct Row2.1;
14244                   struct Row2.2;
14245                   «ˇ
14246                   struct Row4;
14247                   struct» Row5;
14248                   «struct Row6;
14249                   ˇ»
14250                   struct Row9.1;
14251                   struct Row9.2;
14252                   struct Row9.3;
14253                   struct Row8;
14254                   struct Row9;
14255                   struct Row10;"#},
14256        base_text,
14257        &mut cx,
14258    );
14259
14260    // When carets and selections intersect the addition hunks, those are reverted.
14261    // Adjacent carets got merged.
14262    assert_hunk_revert(
14263        indoc! {r#"struct Row;
14264                   ˇ// something on the top
14265                   struct Row1;
14266                   struct Row2;
14267                   struct Roˇw3.1;
14268                   struct Row2.2;
14269                   struct Row2.3;ˇ
14270
14271                   struct Row4;
14272                   struct ˇRow5.1;
14273                   struct Row5.2;
14274                   struct «Rowˇ»5.3;
14275                   struct Row5;
14276                   struct Row6;
14277                   ˇ
14278                   struct Row9.1;
14279                   struct «Rowˇ»9.2;
14280                   struct «ˇRow»9.3;
14281                   struct Row8;
14282                   struct Row9;
14283                   «ˇ// something on bottom»
14284                   struct Row10;"#},
14285        vec![
14286            DiffHunkStatusKind::Added,
14287            DiffHunkStatusKind::Added,
14288            DiffHunkStatusKind::Added,
14289            DiffHunkStatusKind::Added,
14290            DiffHunkStatusKind::Added,
14291        ],
14292        indoc! {r#"struct Row;
14293                   ˇstruct Row1;
14294                   struct Row2;
14295                   ˇ
14296                   struct Row4;
14297                   ˇstruct Row5;
14298                   struct Row6;
14299                   ˇ
14300                   ˇstruct Row8;
14301                   struct Row9;
14302                   ˇstruct Row10;"#},
14303        base_text,
14304        &mut cx,
14305    );
14306}
14307
14308#[gpui::test]
14309async fn test_modification_reverts(cx: &mut TestAppContext) {
14310    init_test(cx, |_| {});
14311    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14312    let base_text = indoc! {r#"
14313        struct Row;
14314        struct Row1;
14315        struct Row2;
14316
14317        struct Row4;
14318        struct Row5;
14319        struct Row6;
14320
14321        struct Row8;
14322        struct Row9;
14323        struct Row10;"#};
14324
14325    // Modification hunks behave the same as the addition ones.
14326    assert_hunk_revert(
14327        indoc! {r#"struct Row;
14328                   struct Row1;
14329                   struct Row33;
14330                   ˇ
14331                   struct Row4;
14332                   struct Row5;
14333                   struct Row6;
14334                   ˇ
14335                   struct Row99;
14336                   struct Row9;
14337                   struct Row10;"#},
14338        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14339        indoc! {r#"struct Row;
14340                   struct Row1;
14341                   struct Row33;
14342                   ˇ
14343                   struct Row4;
14344                   struct Row5;
14345                   struct Row6;
14346                   ˇ
14347                   struct Row99;
14348                   struct Row9;
14349                   struct Row10;"#},
14350        base_text,
14351        &mut cx,
14352    );
14353    assert_hunk_revert(
14354        indoc! {r#"struct Row;
14355                   struct Row1;
14356                   struct Row33;
14357                   «ˇ
14358                   struct Row4;
14359                   struct» Row5;
14360                   «struct Row6;
14361                   ˇ»
14362                   struct Row99;
14363                   struct Row9;
14364                   struct Row10;"#},
14365        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14366        indoc! {r#"struct Row;
14367                   struct Row1;
14368                   struct Row33;
14369                   «ˇ
14370                   struct Row4;
14371                   struct» Row5;
14372                   «struct Row6;
14373                   ˇ»
14374                   struct Row99;
14375                   struct Row9;
14376                   struct Row10;"#},
14377        base_text,
14378        &mut cx,
14379    );
14380
14381    assert_hunk_revert(
14382        indoc! {r#"ˇstruct Row1.1;
14383                   struct Row1;
14384                   «ˇstr»uct Row22;
14385
14386                   struct ˇRow44;
14387                   struct Row5;
14388                   struct «Rˇ»ow66;ˇ
14389
14390                   «struˇ»ct Row88;
14391                   struct Row9;
14392                   struct Row1011;ˇ"#},
14393        vec![
14394            DiffHunkStatusKind::Modified,
14395            DiffHunkStatusKind::Modified,
14396            DiffHunkStatusKind::Modified,
14397            DiffHunkStatusKind::Modified,
14398            DiffHunkStatusKind::Modified,
14399            DiffHunkStatusKind::Modified,
14400        ],
14401        indoc! {r#"struct Row;
14402                   ˇstruct Row1;
14403                   struct Row2;
14404                   ˇ
14405                   struct Row4;
14406                   ˇstruct Row5;
14407                   struct Row6;
14408                   ˇ
14409                   struct Row8;
14410                   ˇstruct Row9;
14411                   struct Row10;ˇ"#},
14412        base_text,
14413        &mut cx,
14414    );
14415}
14416
14417#[gpui::test]
14418async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14419    init_test(cx, |_| {});
14420    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14421    let base_text = indoc! {r#"
14422        one
14423
14424        two
14425        three
14426        "#};
14427
14428    cx.set_head_text(base_text);
14429    cx.set_state("\nˇ\n");
14430    cx.executor().run_until_parked();
14431    cx.update_editor(|editor, _window, cx| {
14432        editor.expand_selected_diff_hunks(cx);
14433    });
14434    cx.executor().run_until_parked();
14435    cx.update_editor(|editor, window, cx| {
14436        editor.backspace(&Default::default(), window, cx);
14437    });
14438    cx.run_until_parked();
14439    cx.assert_state_with_diff(
14440        indoc! {r#"
14441
14442        - two
14443        - threeˇ
14444        +
14445        "#}
14446        .to_string(),
14447    );
14448}
14449
14450#[gpui::test]
14451async fn test_deletion_reverts(cx: &mut TestAppContext) {
14452    init_test(cx, |_| {});
14453    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14454    let base_text = indoc! {r#"struct Row;
14455struct Row1;
14456struct Row2;
14457
14458struct Row4;
14459struct Row5;
14460struct Row6;
14461
14462struct Row8;
14463struct Row9;
14464struct Row10;"#};
14465
14466    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14467    assert_hunk_revert(
14468        indoc! {r#"struct Row;
14469                   struct Row2;
14470
14471                   ˇstruct Row4;
14472                   struct Row5;
14473                   struct Row6;
14474                   ˇ
14475                   struct Row8;
14476                   struct Row10;"#},
14477        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14478        indoc! {r#"struct Row;
14479                   struct Row2;
14480
14481                   ˇstruct Row4;
14482                   struct Row5;
14483                   struct Row6;
14484                   ˇ
14485                   struct Row8;
14486                   struct Row10;"#},
14487        base_text,
14488        &mut cx,
14489    );
14490    assert_hunk_revert(
14491        indoc! {r#"struct Row;
14492                   struct Row2;
14493
14494                   «ˇstruct Row4;
14495                   struct» Row5;
14496                   «struct Row6;
14497                   ˇ»
14498                   struct Row8;
14499                   struct Row10;"#},
14500        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14501        indoc! {r#"struct Row;
14502                   struct Row2;
14503
14504                   «ˇstruct Row4;
14505                   struct» Row5;
14506                   «struct Row6;
14507                   ˇ»
14508                   struct Row8;
14509                   struct Row10;"#},
14510        base_text,
14511        &mut cx,
14512    );
14513
14514    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14515    assert_hunk_revert(
14516        indoc! {r#"struct Row;
14517                   ˇstruct Row2;
14518
14519                   struct Row4;
14520                   struct Row5;
14521                   struct Row6;
14522
14523                   struct Row8;ˇ
14524                   struct Row10;"#},
14525        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14526        indoc! {r#"struct Row;
14527                   struct Row1;
14528                   ˇstruct Row2;
14529
14530                   struct Row4;
14531                   struct Row5;
14532                   struct Row6;
14533
14534                   struct Row8;ˇ
14535                   struct Row9;
14536                   struct Row10;"#},
14537        base_text,
14538        &mut cx,
14539    );
14540    assert_hunk_revert(
14541        indoc! {r#"struct Row;
14542                   struct Row2«ˇ;
14543                   struct Row4;
14544                   struct» Row5;
14545                   «struct Row6;
14546
14547                   struct Row8;ˇ»
14548                   struct Row10;"#},
14549        vec![
14550            DiffHunkStatusKind::Deleted,
14551            DiffHunkStatusKind::Deleted,
14552            DiffHunkStatusKind::Deleted,
14553        ],
14554        indoc! {r#"struct Row;
14555                   struct Row1;
14556                   struct Row2«ˇ;
14557
14558                   struct Row4;
14559                   struct» Row5;
14560                   «struct Row6;
14561
14562                   struct Row8;ˇ»
14563                   struct Row9;
14564                   struct Row10;"#},
14565        base_text,
14566        &mut cx,
14567    );
14568}
14569
14570#[gpui::test]
14571async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14572    init_test(cx, |_| {});
14573
14574    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14575    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14576    let base_text_3 =
14577        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14578
14579    let text_1 = edit_first_char_of_every_line(base_text_1);
14580    let text_2 = edit_first_char_of_every_line(base_text_2);
14581    let text_3 = edit_first_char_of_every_line(base_text_3);
14582
14583    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14584    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14585    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14586
14587    let multibuffer = cx.new(|cx| {
14588        let mut multibuffer = MultiBuffer::new(ReadWrite);
14589        multibuffer.push_excerpts(
14590            buffer_1.clone(),
14591            [
14592                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14593                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14594                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14595            ],
14596            cx,
14597        );
14598        multibuffer.push_excerpts(
14599            buffer_2.clone(),
14600            [
14601                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14602                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14603                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14604            ],
14605            cx,
14606        );
14607        multibuffer.push_excerpts(
14608            buffer_3.clone(),
14609            [
14610                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14611                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14612                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14613            ],
14614            cx,
14615        );
14616        multibuffer
14617    });
14618
14619    let fs = FakeFs::new(cx.executor());
14620    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14621    let (editor, cx) = cx
14622        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14623    editor.update_in(cx, |editor, _window, cx| {
14624        for (buffer, diff_base) in [
14625            (buffer_1.clone(), base_text_1),
14626            (buffer_2.clone(), base_text_2),
14627            (buffer_3.clone(), base_text_3),
14628        ] {
14629            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14630            editor
14631                .buffer
14632                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14633        }
14634    });
14635    cx.executor().run_until_parked();
14636
14637    editor.update_in(cx, |editor, window, cx| {
14638        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}");
14639        editor.select_all(&SelectAll, window, cx);
14640        editor.git_restore(&Default::default(), window, cx);
14641    });
14642    cx.executor().run_until_parked();
14643
14644    // When all ranges are selected, all buffer hunks are reverted.
14645    editor.update(cx, |editor, cx| {
14646        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");
14647    });
14648    buffer_1.update(cx, |buffer, _| {
14649        assert_eq!(buffer.text(), base_text_1);
14650    });
14651    buffer_2.update(cx, |buffer, _| {
14652        assert_eq!(buffer.text(), base_text_2);
14653    });
14654    buffer_3.update(cx, |buffer, _| {
14655        assert_eq!(buffer.text(), base_text_3);
14656    });
14657
14658    editor.update_in(cx, |editor, window, cx| {
14659        editor.undo(&Default::default(), window, cx);
14660    });
14661
14662    editor.update_in(cx, |editor, window, cx| {
14663        editor.change_selections(None, window, cx, |s| {
14664            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14665        });
14666        editor.git_restore(&Default::default(), window, cx);
14667    });
14668
14669    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14670    // but not affect buffer_2 and its related excerpts.
14671    editor.update(cx, |editor, cx| {
14672        assert_eq!(
14673            editor.text(cx),
14674            "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}"
14675        );
14676    });
14677    buffer_1.update(cx, |buffer, _| {
14678        assert_eq!(buffer.text(), base_text_1);
14679    });
14680    buffer_2.update(cx, |buffer, _| {
14681        assert_eq!(
14682            buffer.text(),
14683            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14684        );
14685    });
14686    buffer_3.update(cx, |buffer, _| {
14687        assert_eq!(
14688            buffer.text(),
14689            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14690        );
14691    });
14692
14693    fn edit_first_char_of_every_line(text: &str) -> String {
14694        text.split('\n')
14695            .map(|line| format!("X{}", &line[1..]))
14696            .collect::<Vec<_>>()
14697            .join("\n")
14698    }
14699}
14700
14701#[gpui::test]
14702async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14703    init_test(cx, |_| {});
14704
14705    let cols = 4;
14706    let rows = 10;
14707    let sample_text_1 = sample_text(rows, cols, 'a');
14708    assert_eq!(
14709        sample_text_1,
14710        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14711    );
14712    let sample_text_2 = sample_text(rows, cols, 'l');
14713    assert_eq!(
14714        sample_text_2,
14715        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14716    );
14717    let sample_text_3 = sample_text(rows, cols, 'v');
14718    assert_eq!(
14719        sample_text_3,
14720        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14721    );
14722
14723    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14724    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14725    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14726
14727    let multi_buffer = cx.new(|cx| {
14728        let mut multibuffer = MultiBuffer::new(ReadWrite);
14729        multibuffer.push_excerpts(
14730            buffer_1.clone(),
14731            [
14732                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14733                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14734                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14735            ],
14736            cx,
14737        );
14738        multibuffer.push_excerpts(
14739            buffer_2.clone(),
14740            [
14741                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14742                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14743                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14744            ],
14745            cx,
14746        );
14747        multibuffer.push_excerpts(
14748            buffer_3.clone(),
14749            [
14750                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14751                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14752                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14753            ],
14754            cx,
14755        );
14756        multibuffer
14757    });
14758
14759    let fs = FakeFs::new(cx.executor());
14760    fs.insert_tree(
14761        "/a",
14762        json!({
14763            "main.rs": sample_text_1,
14764            "other.rs": sample_text_2,
14765            "lib.rs": sample_text_3,
14766        }),
14767    )
14768    .await;
14769    let project = Project::test(fs, ["/a".as_ref()], cx).await;
14770    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14771    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14772    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14773        Editor::new(
14774            EditorMode::full(),
14775            multi_buffer,
14776            Some(project.clone()),
14777            window,
14778            cx,
14779        )
14780    });
14781    let multibuffer_item_id = workspace
14782        .update(cx, |workspace, window, cx| {
14783            assert!(
14784                workspace.active_item(cx).is_none(),
14785                "active item should be None before the first item is added"
14786            );
14787            workspace.add_item_to_active_pane(
14788                Box::new(multi_buffer_editor.clone()),
14789                None,
14790                true,
14791                window,
14792                cx,
14793            );
14794            let active_item = workspace
14795                .active_item(cx)
14796                .expect("should have an active item after adding the multi buffer");
14797            assert!(
14798                !active_item.is_singleton(cx),
14799                "A multi buffer was expected to active after adding"
14800            );
14801            active_item.item_id()
14802        })
14803        .unwrap();
14804    cx.executor().run_until_parked();
14805
14806    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14807        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14808            s.select_ranges(Some(1..2))
14809        });
14810        editor.open_excerpts(&OpenExcerpts, window, cx);
14811    });
14812    cx.executor().run_until_parked();
14813    let first_item_id = workspace
14814        .update(cx, |workspace, window, cx| {
14815            let active_item = workspace
14816                .active_item(cx)
14817                .expect("should have an active item after navigating into the 1st buffer");
14818            let first_item_id = active_item.item_id();
14819            assert_ne!(
14820                first_item_id, multibuffer_item_id,
14821                "Should navigate into the 1st buffer and activate it"
14822            );
14823            assert!(
14824                active_item.is_singleton(cx),
14825                "New active item should be a singleton buffer"
14826            );
14827            assert_eq!(
14828                active_item
14829                    .act_as::<Editor>(cx)
14830                    .expect("should have navigated into an editor for the 1st buffer")
14831                    .read(cx)
14832                    .text(cx),
14833                sample_text_1
14834            );
14835
14836            workspace
14837                .go_back(workspace.active_pane().downgrade(), window, cx)
14838                .detach_and_log_err(cx);
14839
14840            first_item_id
14841        })
14842        .unwrap();
14843    cx.executor().run_until_parked();
14844    workspace
14845        .update(cx, |workspace, _, cx| {
14846            let active_item = workspace
14847                .active_item(cx)
14848                .expect("should have an active item after navigating back");
14849            assert_eq!(
14850                active_item.item_id(),
14851                multibuffer_item_id,
14852                "Should navigate back to the multi buffer"
14853            );
14854            assert!(!active_item.is_singleton(cx));
14855        })
14856        .unwrap();
14857
14858    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14859        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14860            s.select_ranges(Some(39..40))
14861        });
14862        editor.open_excerpts(&OpenExcerpts, window, cx);
14863    });
14864    cx.executor().run_until_parked();
14865    let second_item_id = workspace
14866        .update(cx, |workspace, window, cx| {
14867            let active_item = workspace
14868                .active_item(cx)
14869                .expect("should have an active item after navigating into the 2nd buffer");
14870            let second_item_id = active_item.item_id();
14871            assert_ne!(
14872                second_item_id, multibuffer_item_id,
14873                "Should navigate away from the multibuffer"
14874            );
14875            assert_ne!(
14876                second_item_id, first_item_id,
14877                "Should navigate into the 2nd buffer and activate it"
14878            );
14879            assert!(
14880                active_item.is_singleton(cx),
14881                "New active item should be a singleton buffer"
14882            );
14883            assert_eq!(
14884                active_item
14885                    .act_as::<Editor>(cx)
14886                    .expect("should have navigated into an editor")
14887                    .read(cx)
14888                    .text(cx),
14889                sample_text_2
14890            );
14891
14892            workspace
14893                .go_back(workspace.active_pane().downgrade(), window, cx)
14894                .detach_and_log_err(cx);
14895
14896            second_item_id
14897        })
14898        .unwrap();
14899    cx.executor().run_until_parked();
14900    workspace
14901        .update(cx, |workspace, _, cx| {
14902            let active_item = workspace
14903                .active_item(cx)
14904                .expect("should have an active item after navigating back from the 2nd buffer");
14905            assert_eq!(
14906                active_item.item_id(),
14907                multibuffer_item_id,
14908                "Should navigate back from the 2nd buffer to the multi buffer"
14909            );
14910            assert!(!active_item.is_singleton(cx));
14911        })
14912        .unwrap();
14913
14914    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14915        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14916            s.select_ranges(Some(70..70))
14917        });
14918        editor.open_excerpts(&OpenExcerpts, window, cx);
14919    });
14920    cx.executor().run_until_parked();
14921    workspace
14922        .update(cx, |workspace, window, cx| {
14923            let active_item = workspace
14924                .active_item(cx)
14925                .expect("should have an active item after navigating into the 3rd buffer");
14926            let third_item_id = active_item.item_id();
14927            assert_ne!(
14928                third_item_id, multibuffer_item_id,
14929                "Should navigate into the 3rd buffer and activate it"
14930            );
14931            assert_ne!(third_item_id, first_item_id);
14932            assert_ne!(third_item_id, second_item_id);
14933            assert!(
14934                active_item.is_singleton(cx),
14935                "New active item should be a singleton buffer"
14936            );
14937            assert_eq!(
14938                active_item
14939                    .act_as::<Editor>(cx)
14940                    .expect("should have navigated into an editor")
14941                    .read(cx)
14942                    .text(cx),
14943                sample_text_3
14944            );
14945
14946            workspace
14947                .go_back(workspace.active_pane().downgrade(), window, cx)
14948                .detach_and_log_err(cx);
14949        })
14950        .unwrap();
14951    cx.executor().run_until_parked();
14952    workspace
14953        .update(cx, |workspace, _, cx| {
14954            let active_item = workspace
14955                .active_item(cx)
14956                .expect("should have an active item after navigating back from the 3rd buffer");
14957            assert_eq!(
14958                active_item.item_id(),
14959                multibuffer_item_id,
14960                "Should navigate back from the 3rd buffer to the multi buffer"
14961            );
14962            assert!(!active_item.is_singleton(cx));
14963        })
14964        .unwrap();
14965}
14966
14967#[gpui::test]
14968async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14969    init_test(cx, |_| {});
14970
14971    let mut cx = EditorTestContext::new(cx).await;
14972
14973    let diff_base = r#"
14974        use some::mod;
14975
14976        const A: u32 = 42;
14977
14978        fn main() {
14979            println!("hello");
14980
14981            println!("world");
14982        }
14983        "#
14984    .unindent();
14985
14986    cx.set_state(
14987        &r#"
14988        use some::modified;
14989
14990        ˇ
14991        fn main() {
14992            println!("hello there");
14993
14994            println!("around the");
14995            println!("world");
14996        }
14997        "#
14998        .unindent(),
14999    );
15000
15001    cx.set_head_text(&diff_base);
15002    executor.run_until_parked();
15003
15004    cx.update_editor(|editor, window, cx| {
15005        editor.go_to_next_hunk(&GoToHunk, window, cx);
15006        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15007    });
15008    executor.run_until_parked();
15009    cx.assert_state_with_diff(
15010        r#"
15011          use some::modified;
15012
15013
15014          fn main() {
15015        -     println!("hello");
15016        + ˇ    println!("hello there");
15017
15018              println!("around the");
15019              println!("world");
15020          }
15021        "#
15022        .unindent(),
15023    );
15024
15025    cx.update_editor(|editor, window, cx| {
15026        for _ in 0..2 {
15027            editor.go_to_next_hunk(&GoToHunk, window, cx);
15028            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15029        }
15030    });
15031    executor.run_until_parked();
15032    cx.assert_state_with_diff(
15033        r#"
15034        - use some::mod;
15035        + ˇuse some::modified;
15036
15037
15038          fn main() {
15039        -     println!("hello");
15040        +     println!("hello there");
15041
15042        +     println!("around the");
15043              println!("world");
15044          }
15045        "#
15046        .unindent(),
15047    );
15048
15049    cx.update_editor(|editor, window, cx| {
15050        editor.go_to_next_hunk(&GoToHunk, window, cx);
15051        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15052    });
15053    executor.run_until_parked();
15054    cx.assert_state_with_diff(
15055        r#"
15056        - use some::mod;
15057        + use some::modified;
15058
15059        - const A: u32 = 42;
15060          ˇ
15061          fn main() {
15062        -     println!("hello");
15063        +     println!("hello there");
15064
15065        +     println!("around the");
15066              println!("world");
15067          }
15068        "#
15069        .unindent(),
15070    );
15071
15072    cx.update_editor(|editor, window, cx| {
15073        editor.cancel(&Cancel, window, cx);
15074    });
15075
15076    cx.assert_state_with_diff(
15077        r#"
15078          use some::modified;
15079
15080          ˇ
15081          fn main() {
15082              println!("hello there");
15083
15084              println!("around the");
15085              println!("world");
15086          }
15087        "#
15088        .unindent(),
15089    );
15090}
15091
15092#[gpui::test]
15093async fn test_diff_base_change_with_expanded_diff_hunks(
15094    executor: BackgroundExecutor,
15095    cx: &mut TestAppContext,
15096) {
15097    init_test(cx, |_| {});
15098
15099    let mut cx = EditorTestContext::new(cx).await;
15100
15101    let diff_base = r#"
15102        use some::mod1;
15103        use some::mod2;
15104
15105        const A: u32 = 42;
15106        const B: u32 = 42;
15107        const C: u32 = 42;
15108
15109        fn main() {
15110            println!("hello");
15111
15112            println!("world");
15113        }
15114        "#
15115    .unindent();
15116
15117    cx.set_state(
15118        &r#"
15119        use some::mod2;
15120
15121        const A: u32 = 42;
15122        const C: u32 = 42;
15123
15124        fn main(ˇ) {
15125            //println!("hello");
15126
15127            println!("world");
15128            //
15129            //
15130        }
15131        "#
15132        .unindent(),
15133    );
15134
15135    cx.set_head_text(&diff_base);
15136    executor.run_until_parked();
15137
15138    cx.update_editor(|editor, window, cx| {
15139        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15140    });
15141    executor.run_until_parked();
15142    cx.assert_state_with_diff(
15143        r#"
15144        - use some::mod1;
15145          use some::mod2;
15146
15147          const A: u32 = 42;
15148        - const B: u32 = 42;
15149          const C: u32 = 42;
15150
15151          fn main(ˇ) {
15152        -     println!("hello");
15153        +     //println!("hello");
15154
15155              println!("world");
15156        +     //
15157        +     //
15158          }
15159        "#
15160        .unindent(),
15161    );
15162
15163    cx.set_head_text("new diff base!");
15164    executor.run_until_parked();
15165    cx.assert_state_with_diff(
15166        r#"
15167        - new diff base!
15168        + use some::mod2;
15169        +
15170        + const A: u32 = 42;
15171        + const C: u32 = 42;
15172        +
15173        + fn main(ˇ) {
15174        +     //println!("hello");
15175        +
15176        +     println!("world");
15177        +     //
15178        +     //
15179        + }
15180        "#
15181        .unindent(),
15182    );
15183}
15184
15185#[gpui::test]
15186async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15187    init_test(cx, |_| {});
15188
15189    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15190    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15191    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15192    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15193    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15194    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15195
15196    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15197    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15198    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15199
15200    let multi_buffer = cx.new(|cx| {
15201        let mut multibuffer = MultiBuffer::new(ReadWrite);
15202        multibuffer.push_excerpts(
15203            buffer_1.clone(),
15204            [
15205                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15206                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15207                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15208            ],
15209            cx,
15210        );
15211        multibuffer.push_excerpts(
15212            buffer_2.clone(),
15213            [
15214                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15215                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15216                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15217            ],
15218            cx,
15219        );
15220        multibuffer.push_excerpts(
15221            buffer_3.clone(),
15222            [
15223                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15224                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15225                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15226            ],
15227            cx,
15228        );
15229        multibuffer
15230    });
15231
15232    let editor =
15233        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15234    editor
15235        .update(cx, |editor, _window, cx| {
15236            for (buffer, diff_base) in [
15237                (buffer_1.clone(), file_1_old),
15238                (buffer_2.clone(), file_2_old),
15239                (buffer_3.clone(), file_3_old),
15240            ] {
15241                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15242                editor
15243                    .buffer
15244                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15245            }
15246        })
15247        .unwrap();
15248
15249    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15250    cx.run_until_parked();
15251
15252    cx.assert_editor_state(
15253        &"
15254            ˇaaa
15255            ccc
15256            ddd
15257
15258            ggg
15259            hhh
15260
15261
15262            lll
15263            mmm
15264            NNN
15265
15266            qqq
15267            rrr
15268
15269            uuu
15270            111
15271            222
15272            333
15273
15274            666
15275            777
15276
15277            000
15278            !!!"
15279        .unindent(),
15280    );
15281
15282    cx.update_editor(|editor, window, cx| {
15283        editor.select_all(&SelectAll, window, cx);
15284        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15285    });
15286    cx.executor().run_until_parked();
15287
15288    cx.assert_state_with_diff(
15289        "
15290            «aaa
15291          - bbb
15292            ccc
15293            ddd
15294
15295            ggg
15296            hhh
15297
15298
15299            lll
15300            mmm
15301          - nnn
15302          + NNN
15303
15304            qqq
15305            rrr
15306
15307            uuu
15308            111
15309            222
15310            333
15311
15312          + 666
15313            777
15314
15315            000
15316            !!!ˇ»"
15317            .unindent(),
15318    );
15319}
15320
15321#[gpui::test]
15322async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15323    init_test(cx, |_| {});
15324
15325    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15326    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15327
15328    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15329    let multi_buffer = cx.new(|cx| {
15330        let mut multibuffer = MultiBuffer::new(ReadWrite);
15331        multibuffer.push_excerpts(
15332            buffer.clone(),
15333            [
15334                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15335                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15336                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15337            ],
15338            cx,
15339        );
15340        multibuffer
15341    });
15342
15343    let editor =
15344        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15345    editor
15346        .update(cx, |editor, _window, cx| {
15347            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15348            editor
15349                .buffer
15350                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15351        })
15352        .unwrap();
15353
15354    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15355    cx.run_until_parked();
15356
15357    cx.update_editor(|editor, window, cx| {
15358        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15359    });
15360    cx.executor().run_until_parked();
15361
15362    // When the start of a hunk coincides with the start of its excerpt,
15363    // the hunk is expanded. When the start of a a hunk is earlier than
15364    // the start of its excerpt, the hunk is not expanded.
15365    cx.assert_state_with_diff(
15366        "
15367            ˇaaa
15368          - bbb
15369          + BBB
15370
15371          - ddd
15372          - eee
15373          + DDD
15374          + EEE
15375            fff
15376
15377            iii
15378        "
15379        .unindent(),
15380    );
15381}
15382
15383#[gpui::test]
15384async fn test_edits_around_expanded_insertion_hunks(
15385    executor: BackgroundExecutor,
15386    cx: &mut TestAppContext,
15387) {
15388    init_test(cx, |_| {});
15389
15390    let mut cx = EditorTestContext::new(cx).await;
15391
15392    let diff_base = r#"
15393        use some::mod1;
15394        use some::mod2;
15395
15396        const A: u32 = 42;
15397
15398        fn main() {
15399            println!("hello");
15400
15401            println!("world");
15402        }
15403        "#
15404    .unindent();
15405    executor.run_until_parked();
15406    cx.set_state(
15407        &r#"
15408        use some::mod1;
15409        use some::mod2;
15410
15411        const A: u32 = 42;
15412        const B: u32 = 42;
15413        const C: u32 = 42;
15414        ˇ
15415
15416        fn main() {
15417            println!("hello");
15418
15419            println!("world");
15420        }
15421        "#
15422        .unindent(),
15423    );
15424
15425    cx.set_head_text(&diff_base);
15426    executor.run_until_parked();
15427
15428    cx.update_editor(|editor, window, cx| {
15429        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15430    });
15431    executor.run_until_parked();
15432
15433    cx.assert_state_with_diff(
15434        r#"
15435        use some::mod1;
15436        use some::mod2;
15437
15438        const A: u32 = 42;
15439      + const B: u32 = 42;
15440      + const C: u32 = 42;
15441      + ˇ
15442
15443        fn main() {
15444            println!("hello");
15445
15446            println!("world");
15447        }
15448      "#
15449        .unindent(),
15450    );
15451
15452    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15453    executor.run_until_parked();
15454
15455    cx.assert_state_with_diff(
15456        r#"
15457        use some::mod1;
15458        use some::mod2;
15459
15460        const A: u32 = 42;
15461      + const B: u32 = 42;
15462      + const C: u32 = 42;
15463      + const D: u32 = 42;
15464      + ˇ
15465
15466        fn main() {
15467            println!("hello");
15468
15469            println!("world");
15470        }
15471      "#
15472        .unindent(),
15473    );
15474
15475    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15476    executor.run_until_parked();
15477
15478    cx.assert_state_with_diff(
15479        r#"
15480        use some::mod1;
15481        use some::mod2;
15482
15483        const A: u32 = 42;
15484      + const B: u32 = 42;
15485      + const C: u32 = 42;
15486      + const D: u32 = 42;
15487      + const E: u32 = 42;
15488      + ˇ
15489
15490        fn main() {
15491            println!("hello");
15492
15493            println!("world");
15494        }
15495      "#
15496        .unindent(),
15497    );
15498
15499    cx.update_editor(|editor, window, cx| {
15500        editor.delete_line(&DeleteLine, window, cx);
15501    });
15502    executor.run_until_parked();
15503
15504    cx.assert_state_with_diff(
15505        r#"
15506        use some::mod1;
15507        use some::mod2;
15508
15509        const A: u32 = 42;
15510      + const B: u32 = 42;
15511      + const C: u32 = 42;
15512      + const D: u32 = 42;
15513      + const E: u32 = 42;
15514        ˇ
15515        fn main() {
15516            println!("hello");
15517
15518            println!("world");
15519        }
15520      "#
15521        .unindent(),
15522    );
15523
15524    cx.update_editor(|editor, window, cx| {
15525        editor.move_up(&MoveUp, window, cx);
15526        editor.delete_line(&DeleteLine, window, cx);
15527        editor.move_up(&MoveUp, window, cx);
15528        editor.delete_line(&DeleteLine, window, cx);
15529        editor.move_up(&MoveUp, window, cx);
15530        editor.delete_line(&DeleteLine, window, cx);
15531    });
15532    executor.run_until_parked();
15533    cx.assert_state_with_diff(
15534        r#"
15535        use some::mod1;
15536        use some::mod2;
15537
15538        const A: u32 = 42;
15539      + const B: u32 = 42;
15540        ˇ
15541        fn main() {
15542            println!("hello");
15543
15544            println!("world");
15545        }
15546      "#
15547        .unindent(),
15548    );
15549
15550    cx.update_editor(|editor, window, cx| {
15551        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15552        editor.delete_line(&DeleteLine, window, cx);
15553    });
15554    executor.run_until_parked();
15555    cx.assert_state_with_diff(
15556        r#"
15557        ˇ
15558        fn main() {
15559            println!("hello");
15560
15561            println!("world");
15562        }
15563      "#
15564        .unindent(),
15565    );
15566}
15567
15568#[gpui::test]
15569async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15570    init_test(cx, |_| {});
15571
15572    let mut cx = EditorTestContext::new(cx).await;
15573    cx.set_head_text(indoc! { "
15574        one
15575        two
15576        three
15577        four
15578        five
15579        "
15580    });
15581    cx.set_state(indoc! { "
15582        one
15583        ˇthree
15584        five
15585    "});
15586    cx.run_until_parked();
15587    cx.update_editor(|editor, window, cx| {
15588        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15589    });
15590    cx.assert_state_with_diff(
15591        indoc! { "
15592        one
15593      - two
15594        ˇthree
15595      - four
15596        five
15597    "}
15598        .to_string(),
15599    );
15600    cx.update_editor(|editor, window, cx| {
15601        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15602    });
15603
15604    cx.assert_state_with_diff(
15605        indoc! { "
15606        one
15607        ˇthree
15608        five
15609    "}
15610        .to_string(),
15611    );
15612
15613    cx.set_state(indoc! { "
15614        one
15615        ˇTWO
15616        three
15617        four
15618        five
15619    "});
15620    cx.run_until_parked();
15621    cx.update_editor(|editor, window, cx| {
15622        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15623    });
15624
15625    cx.assert_state_with_diff(
15626        indoc! { "
15627            one
15628          - two
15629          + ˇTWO
15630            three
15631            four
15632            five
15633        "}
15634        .to_string(),
15635    );
15636    cx.update_editor(|editor, window, cx| {
15637        editor.move_up(&Default::default(), window, cx);
15638        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15639    });
15640    cx.assert_state_with_diff(
15641        indoc! { "
15642            one
15643            ˇTWO
15644            three
15645            four
15646            five
15647        "}
15648        .to_string(),
15649    );
15650}
15651
15652#[gpui::test]
15653async fn test_edits_around_expanded_deletion_hunks(
15654    executor: BackgroundExecutor,
15655    cx: &mut TestAppContext,
15656) {
15657    init_test(cx, |_| {});
15658
15659    let mut cx = EditorTestContext::new(cx).await;
15660
15661    let diff_base = r#"
15662        use some::mod1;
15663        use some::mod2;
15664
15665        const A: u32 = 42;
15666        const B: u32 = 42;
15667        const C: u32 = 42;
15668
15669
15670        fn main() {
15671            println!("hello");
15672
15673            println!("world");
15674        }
15675    "#
15676    .unindent();
15677    executor.run_until_parked();
15678    cx.set_state(
15679        &r#"
15680        use some::mod1;
15681        use some::mod2;
15682
15683        ˇconst B: u32 = 42;
15684        const C: u32 = 42;
15685
15686
15687        fn main() {
15688            println!("hello");
15689
15690            println!("world");
15691        }
15692        "#
15693        .unindent(),
15694    );
15695
15696    cx.set_head_text(&diff_base);
15697    executor.run_until_parked();
15698
15699    cx.update_editor(|editor, window, cx| {
15700        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15701    });
15702    executor.run_until_parked();
15703
15704    cx.assert_state_with_diff(
15705        r#"
15706        use some::mod1;
15707        use some::mod2;
15708
15709      - const A: u32 = 42;
15710        ˇconst B: u32 = 42;
15711        const C: u32 = 42;
15712
15713
15714        fn main() {
15715            println!("hello");
15716
15717            println!("world");
15718        }
15719      "#
15720        .unindent(),
15721    );
15722
15723    cx.update_editor(|editor, window, cx| {
15724        editor.delete_line(&DeleteLine, window, cx);
15725    });
15726    executor.run_until_parked();
15727    cx.assert_state_with_diff(
15728        r#"
15729        use some::mod1;
15730        use some::mod2;
15731
15732      - const A: u32 = 42;
15733      - const B: u32 = 42;
15734        ˇconst C: u32 = 42;
15735
15736
15737        fn main() {
15738            println!("hello");
15739
15740            println!("world");
15741        }
15742      "#
15743        .unindent(),
15744    );
15745
15746    cx.update_editor(|editor, window, cx| {
15747        editor.delete_line(&DeleteLine, window, cx);
15748    });
15749    executor.run_until_parked();
15750    cx.assert_state_with_diff(
15751        r#"
15752        use some::mod1;
15753        use some::mod2;
15754
15755      - const A: u32 = 42;
15756      - const B: u32 = 42;
15757      - const C: u32 = 42;
15758        ˇ
15759
15760        fn main() {
15761            println!("hello");
15762
15763            println!("world");
15764        }
15765      "#
15766        .unindent(),
15767    );
15768
15769    cx.update_editor(|editor, window, cx| {
15770        editor.handle_input("replacement", window, cx);
15771    });
15772    executor.run_until_parked();
15773    cx.assert_state_with_diff(
15774        r#"
15775        use some::mod1;
15776        use some::mod2;
15777
15778      - const A: u32 = 42;
15779      - const B: u32 = 42;
15780      - const C: u32 = 42;
15781      -
15782      + replacementˇ
15783
15784        fn main() {
15785            println!("hello");
15786
15787            println!("world");
15788        }
15789      "#
15790        .unindent(),
15791    );
15792}
15793
15794#[gpui::test]
15795async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15796    init_test(cx, |_| {});
15797
15798    let mut cx = EditorTestContext::new(cx).await;
15799
15800    let base_text = r#"
15801        one
15802        two
15803        three
15804        four
15805        five
15806    "#
15807    .unindent();
15808    executor.run_until_parked();
15809    cx.set_state(
15810        &r#"
15811        one
15812        two
15813        fˇour
15814        five
15815        "#
15816        .unindent(),
15817    );
15818
15819    cx.set_head_text(&base_text);
15820    executor.run_until_parked();
15821
15822    cx.update_editor(|editor, window, cx| {
15823        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15824    });
15825    executor.run_until_parked();
15826
15827    cx.assert_state_with_diff(
15828        r#"
15829          one
15830          two
15831        - three
15832          fˇour
15833          five
15834        "#
15835        .unindent(),
15836    );
15837
15838    cx.update_editor(|editor, window, cx| {
15839        editor.backspace(&Backspace, window, cx);
15840        editor.backspace(&Backspace, window, cx);
15841    });
15842    executor.run_until_parked();
15843    cx.assert_state_with_diff(
15844        r#"
15845          one
15846          two
15847        - threeˇ
15848        - four
15849        + our
15850          five
15851        "#
15852        .unindent(),
15853    );
15854}
15855
15856#[gpui::test]
15857async fn test_edit_after_expanded_modification_hunk(
15858    executor: BackgroundExecutor,
15859    cx: &mut TestAppContext,
15860) {
15861    init_test(cx, |_| {});
15862
15863    let mut cx = EditorTestContext::new(cx).await;
15864
15865    let diff_base = r#"
15866        use some::mod1;
15867        use some::mod2;
15868
15869        const A: u32 = 42;
15870        const B: u32 = 42;
15871        const C: u32 = 42;
15872        const D: u32 = 42;
15873
15874
15875        fn main() {
15876            println!("hello");
15877
15878            println!("world");
15879        }"#
15880    .unindent();
15881
15882    cx.set_state(
15883        &r#"
15884        use some::mod1;
15885        use some::mod2;
15886
15887        const A: u32 = 42;
15888        const B: u32 = 42;
15889        const C: u32 = 43ˇ
15890        const D: u32 = 42;
15891
15892
15893        fn main() {
15894            println!("hello");
15895
15896            println!("world");
15897        }"#
15898        .unindent(),
15899    );
15900
15901    cx.set_head_text(&diff_base);
15902    executor.run_until_parked();
15903    cx.update_editor(|editor, window, cx| {
15904        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15905    });
15906    executor.run_until_parked();
15907
15908    cx.assert_state_with_diff(
15909        r#"
15910        use some::mod1;
15911        use some::mod2;
15912
15913        const A: u32 = 42;
15914        const B: u32 = 42;
15915      - const C: u32 = 42;
15916      + const C: u32 = 43ˇ
15917        const D: u32 = 42;
15918
15919
15920        fn main() {
15921            println!("hello");
15922
15923            println!("world");
15924        }"#
15925        .unindent(),
15926    );
15927
15928    cx.update_editor(|editor, window, cx| {
15929        editor.handle_input("\nnew_line\n", window, cx);
15930    });
15931    executor.run_until_parked();
15932
15933    cx.assert_state_with_diff(
15934        r#"
15935        use some::mod1;
15936        use some::mod2;
15937
15938        const A: u32 = 42;
15939        const B: u32 = 42;
15940      - const C: u32 = 42;
15941      + const C: u32 = 43
15942      + new_line
15943      + ˇ
15944        const D: u32 = 42;
15945
15946
15947        fn main() {
15948            println!("hello");
15949
15950            println!("world");
15951        }"#
15952        .unindent(),
15953    );
15954}
15955
15956#[gpui::test]
15957async fn test_stage_and_unstage_added_file_hunk(
15958    executor: BackgroundExecutor,
15959    cx: &mut TestAppContext,
15960) {
15961    init_test(cx, |_| {});
15962
15963    let mut cx = EditorTestContext::new(cx).await;
15964    cx.update_editor(|editor, _, cx| {
15965        editor.set_expand_all_diff_hunks(cx);
15966    });
15967
15968    let working_copy = r#"
15969            ˇfn main() {
15970                println!("hello, world!");
15971            }
15972        "#
15973    .unindent();
15974
15975    cx.set_state(&working_copy);
15976    executor.run_until_parked();
15977
15978    cx.assert_state_with_diff(
15979        r#"
15980            + ˇfn main() {
15981            +     println!("hello, world!");
15982            + }
15983        "#
15984        .unindent(),
15985    );
15986    cx.assert_index_text(None);
15987
15988    cx.update_editor(|editor, window, cx| {
15989        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15990    });
15991    executor.run_until_parked();
15992    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15993    cx.assert_state_with_diff(
15994        r#"
15995            + ˇfn main() {
15996            +     println!("hello, world!");
15997            + }
15998        "#
15999        .unindent(),
16000    );
16001
16002    cx.update_editor(|editor, window, cx| {
16003        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16004    });
16005    executor.run_until_parked();
16006    cx.assert_index_text(None);
16007}
16008
16009async fn setup_indent_guides_editor(
16010    text: &str,
16011    cx: &mut TestAppContext,
16012) -> (BufferId, EditorTestContext) {
16013    init_test(cx, |_| {});
16014
16015    let mut cx = EditorTestContext::new(cx).await;
16016
16017    let buffer_id = cx.update_editor(|editor, window, cx| {
16018        editor.set_text(text, window, cx);
16019        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16020
16021        buffer_ids[0]
16022    });
16023
16024    (buffer_id, cx)
16025}
16026
16027fn assert_indent_guides(
16028    range: Range<u32>,
16029    expected: Vec<IndentGuide>,
16030    active_indices: Option<Vec<usize>>,
16031    cx: &mut EditorTestContext,
16032) {
16033    let indent_guides = cx.update_editor(|editor, window, cx| {
16034        let snapshot = editor.snapshot(window, cx).display_snapshot;
16035        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16036            editor,
16037            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16038            true,
16039            &snapshot,
16040            cx,
16041        );
16042
16043        indent_guides.sort_by(|a, b| {
16044            a.depth.cmp(&b.depth).then(
16045                a.start_row
16046                    .cmp(&b.start_row)
16047                    .then(a.end_row.cmp(&b.end_row)),
16048            )
16049        });
16050        indent_guides
16051    });
16052
16053    if let Some(expected) = active_indices {
16054        let active_indices = cx.update_editor(|editor, window, cx| {
16055            let snapshot = editor.snapshot(window, cx).display_snapshot;
16056            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16057        });
16058
16059        assert_eq!(
16060            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16061            expected,
16062            "Active indent guide indices do not match"
16063        );
16064    }
16065
16066    assert_eq!(indent_guides, expected, "Indent guides do not match");
16067}
16068
16069fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16070    IndentGuide {
16071        buffer_id,
16072        start_row: MultiBufferRow(start_row),
16073        end_row: MultiBufferRow(end_row),
16074        depth,
16075        tab_size: 4,
16076        settings: IndentGuideSettings {
16077            enabled: true,
16078            line_width: 1,
16079            active_line_width: 1,
16080            ..Default::default()
16081        },
16082    }
16083}
16084
16085#[gpui::test]
16086async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16087    let (buffer_id, mut cx) = setup_indent_guides_editor(
16088        &"
16089    fn main() {
16090        let a = 1;
16091    }"
16092        .unindent(),
16093        cx,
16094    )
16095    .await;
16096
16097    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16098}
16099
16100#[gpui::test]
16101async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16102    let (buffer_id, mut cx) = setup_indent_guides_editor(
16103        &"
16104    fn main() {
16105        let a = 1;
16106        let b = 2;
16107    }"
16108        .unindent(),
16109        cx,
16110    )
16111    .await;
16112
16113    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16114}
16115
16116#[gpui::test]
16117async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16118    let (buffer_id, mut cx) = setup_indent_guides_editor(
16119        &"
16120    fn main() {
16121        let a = 1;
16122        if a == 3 {
16123            let b = 2;
16124        } else {
16125            let c = 3;
16126        }
16127    }"
16128        .unindent(),
16129        cx,
16130    )
16131    .await;
16132
16133    assert_indent_guides(
16134        0..8,
16135        vec![
16136            indent_guide(buffer_id, 1, 6, 0),
16137            indent_guide(buffer_id, 3, 3, 1),
16138            indent_guide(buffer_id, 5, 5, 1),
16139        ],
16140        None,
16141        &mut cx,
16142    );
16143}
16144
16145#[gpui::test]
16146async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16147    let (buffer_id, mut cx) = setup_indent_guides_editor(
16148        &"
16149    fn main() {
16150        let a = 1;
16151            let b = 2;
16152        let c = 3;
16153    }"
16154        .unindent(),
16155        cx,
16156    )
16157    .await;
16158
16159    assert_indent_guides(
16160        0..5,
16161        vec![
16162            indent_guide(buffer_id, 1, 3, 0),
16163            indent_guide(buffer_id, 2, 2, 1),
16164        ],
16165        None,
16166        &mut cx,
16167    );
16168}
16169
16170#[gpui::test]
16171async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16172    let (buffer_id, mut cx) = setup_indent_guides_editor(
16173        &"
16174        fn main() {
16175            let a = 1;
16176
16177            let c = 3;
16178        }"
16179        .unindent(),
16180        cx,
16181    )
16182    .await;
16183
16184    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16185}
16186
16187#[gpui::test]
16188async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16189    let (buffer_id, mut cx) = setup_indent_guides_editor(
16190        &"
16191        fn main() {
16192            let a = 1;
16193
16194            let c = 3;
16195
16196            if a == 3 {
16197                let b = 2;
16198            } else {
16199                let c = 3;
16200            }
16201        }"
16202        .unindent(),
16203        cx,
16204    )
16205    .await;
16206
16207    assert_indent_guides(
16208        0..11,
16209        vec![
16210            indent_guide(buffer_id, 1, 9, 0),
16211            indent_guide(buffer_id, 6, 6, 1),
16212            indent_guide(buffer_id, 8, 8, 1),
16213        ],
16214        None,
16215        &mut cx,
16216    );
16217}
16218
16219#[gpui::test]
16220async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16221    let (buffer_id, mut cx) = setup_indent_guides_editor(
16222        &"
16223        fn main() {
16224            let a = 1;
16225
16226            let c = 3;
16227
16228            if a == 3 {
16229                let b = 2;
16230            } else {
16231                let c = 3;
16232            }
16233        }"
16234        .unindent(),
16235        cx,
16236    )
16237    .await;
16238
16239    assert_indent_guides(
16240        1..11,
16241        vec![
16242            indent_guide(buffer_id, 1, 9, 0),
16243            indent_guide(buffer_id, 6, 6, 1),
16244            indent_guide(buffer_id, 8, 8, 1),
16245        ],
16246        None,
16247        &mut cx,
16248    );
16249}
16250
16251#[gpui::test]
16252async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16253    let (buffer_id, mut cx) = setup_indent_guides_editor(
16254        &"
16255        fn main() {
16256            let a = 1;
16257
16258            let c = 3;
16259
16260            if a == 3 {
16261                let b = 2;
16262            } else {
16263                let c = 3;
16264            }
16265        }"
16266        .unindent(),
16267        cx,
16268    )
16269    .await;
16270
16271    assert_indent_guides(
16272        1..10,
16273        vec![
16274            indent_guide(buffer_id, 1, 9, 0),
16275            indent_guide(buffer_id, 6, 6, 1),
16276            indent_guide(buffer_id, 8, 8, 1),
16277        ],
16278        None,
16279        &mut cx,
16280    );
16281}
16282
16283#[gpui::test]
16284async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16285    let (buffer_id, mut cx) = setup_indent_guides_editor(
16286        &"
16287        block1
16288            block2
16289                block3
16290                    block4
16291            block2
16292        block1
16293        block1"
16294            .unindent(),
16295        cx,
16296    )
16297    .await;
16298
16299    assert_indent_guides(
16300        1..10,
16301        vec![
16302            indent_guide(buffer_id, 1, 4, 0),
16303            indent_guide(buffer_id, 2, 3, 1),
16304            indent_guide(buffer_id, 3, 3, 2),
16305        ],
16306        None,
16307        &mut cx,
16308    );
16309}
16310
16311#[gpui::test]
16312async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16313    let (buffer_id, mut cx) = setup_indent_guides_editor(
16314        &"
16315        block1
16316            block2
16317                block3
16318
16319        block1
16320        block1"
16321            .unindent(),
16322        cx,
16323    )
16324    .await;
16325
16326    assert_indent_guides(
16327        0..6,
16328        vec![
16329            indent_guide(buffer_id, 1, 2, 0),
16330            indent_guide(buffer_id, 2, 2, 1),
16331        ],
16332        None,
16333        &mut cx,
16334    );
16335}
16336
16337#[gpui::test]
16338async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16339    let (buffer_id, mut cx) = setup_indent_guides_editor(
16340        &"
16341        block1
16342
16343
16344
16345            block2
16346        "
16347        .unindent(),
16348        cx,
16349    )
16350    .await;
16351
16352    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16353}
16354
16355#[gpui::test]
16356async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16357    let (buffer_id, mut cx) = setup_indent_guides_editor(
16358        &"
16359        def a:
16360        \tb = 3
16361        \tif True:
16362        \t\tc = 4
16363        \t\td = 5
16364        \tprint(b)
16365        "
16366        .unindent(),
16367        cx,
16368    )
16369    .await;
16370
16371    assert_indent_guides(
16372        0..6,
16373        vec![
16374            indent_guide(buffer_id, 1, 6, 0),
16375            indent_guide(buffer_id, 3, 4, 1),
16376        ],
16377        None,
16378        &mut cx,
16379    );
16380}
16381
16382#[gpui::test]
16383async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16384    let (buffer_id, mut cx) = setup_indent_guides_editor(
16385        &"
16386    fn main() {
16387        let a = 1;
16388    }"
16389        .unindent(),
16390        cx,
16391    )
16392    .await;
16393
16394    cx.update_editor(|editor, window, cx| {
16395        editor.change_selections(None, window, cx, |s| {
16396            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16397        });
16398    });
16399
16400    assert_indent_guides(
16401        0..3,
16402        vec![indent_guide(buffer_id, 1, 1, 0)],
16403        Some(vec![0]),
16404        &mut cx,
16405    );
16406}
16407
16408#[gpui::test]
16409async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16410    let (buffer_id, mut cx) = setup_indent_guides_editor(
16411        &"
16412    fn main() {
16413        if 1 == 2 {
16414            let a = 1;
16415        }
16416    }"
16417        .unindent(),
16418        cx,
16419    )
16420    .await;
16421
16422    cx.update_editor(|editor, window, cx| {
16423        editor.change_selections(None, window, cx, |s| {
16424            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16425        });
16426    });
16427
16428    assert_indent_guides(
16429        0..4,
16430        vec![
16431            indent_guide(buffer_id, 1, 3, 0),
16432            indent_guide(buffer_id, 2, 2, 1),
16433        ],
16434        Some(vec![1]),
16435        &mut cx,
16436    );
16437
16438    cx.update_editor(|editor, window, cx| {
16439        editor.change_selections(None, window, cx, |s| {
16440            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16441        });
16442    });
16443
16444    assert_indent_guides(
16445        0..4,
16446        vec![
16447            indent_guide(buffer_id, 1, 3, 0),
16448            indent_guide(buffer_id, 2, 2, 1),
16449        ],
16450        Some(vec![1]),
16451        &mut cx,
16452    );
16453
16454    cx.update_editor(|editor, window, cx| {
16455        editor.change_selections(None, window, cx, |s| {
16456            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16457        });
16458    });
16459
16460    assert_indent_guides(
16461        0..4,
16462        vec![
16463            indent_guide(buffer_id, 1, 3, 0),
16464            indent_guide(buffer_id, 2, 2, 1),
16465        ],
16466        Some(vec![0]),
16467        &mut cx,
16468    );
16469}
16470
16471#[gpui::test]
16472async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16473    let (buffer_id, mut cx) = setup_indent_guides_editor(
16474        &"
16475    fn main() {
16476        let a = 1;
16477
16478        let b = 2;
16479    }"
16480        .unindent(),
16481        cx,
16482    )
16483    .await;
16484
16485    cx.update_editor(|editor, window, cx| {
16486        editor.change_selections(None, window, cx, |s| {
16487            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16488        });
16489    });
16490
16491    assert_indent_guides(
16492        0..5,
16493        vec![indent_guide(buffer_id, 1, 3, 0)],
16494        Some(vec![0]),
16495        &mut cx,
16496    );
16497}
16498
16499#[gpui::test]
16500async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16501    let (buffer_id, mut cx) = setup_indent_guides_editor(
16502        &"
16503    def m:
16504        a = 1
16505        pass"
16506            .unindent(),
16507        cx,
16508    )
16509    .await;
16510
16511    cx.update_editor(|editor, window, cx| {
16512        editor.change_selections(None, window, cx, |s| {
16513            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16514        });
16515    });
16516
16517    assert_indent_guides(
16518        0..3,
16519        vec![indent_guide(buffer_id, 1, 2, 0)],
16520        Some(vec![0]),
16521        &mut cx,
16522    );
16523}
16524
16525#[gpui::test]
16526async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16527    init_test(cx, |_| {});
16528    let mut cx = EditorTestContext::new(cx).await;
16529    let text = indoc! {
16530        "
16531        impl A {
16532            fn b() {
16533                0;
16534                3;
16535                5;
16536                6;
16537                7;
16538            }
16539        }
16540        "
16541    };
16542    let base_text = indoc! {
16543        "
16544        impl A {
16545            fn b() {
16546                0;
16547                1;
16548                2;
16549                3;
16550                4;
16551            }
16552            fn c() {
16553                5;
16554                6;
16555                7;
16556            }
16557        }
16558        "
16559    };
16560
16561    cx.update_editor(|editor, window, cx| {
16562        editor.set_text(text, window, cx);
16563
16564        editor.buffer().update(cx, |multibuffer, cx| {
16565            let buffer = multibuffer.as_singleton().unwrap();
16566            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16567
16568            multibuffer.set_all_diff_hunks_expanded(cx);
16569            multibuffer.add_diff(diff, cx);
16570
16571            buffer.read(cx).remote_id()
16572        })
16573    });
16574    cx.run_until_parked();
16575
16576    cx.assert_state_with_diff(
16577        indoc! { "
16578          impl A {
16579              fn b() {
16580                  0;
16581        -         1;
16582        -         2;
16583                  3;
16584        -         4;
16585        -     }
16586        -     fn c() {
16587                  5;
16588                  6;
16589                  7;
16590              }
16591          }
16592          ˇ"
16593        }
16594        .to_string(),
16595    );
16596
16597    let mut actual_guides = cx.update_editor(|editor, window, cx| {
16598        editor
16599            .snapshot(window, cx)
16600            .buffer_snapshot
16601            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16602            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16603            .collect::<Vec<_>>()
16604    });
16605    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16606    assert_eq!(
16607        actual_guides,
16608        vec![
16609            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16610            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16611            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16612        ]
16613    );
16614}
16615
16616#[gpui::test]
16617async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16618    init_test(cx, |_| {});
16619    let mut cx = EditorTestContext::new(cx).await;
16620
16621    let diff_base = r#"
16622        a
16623        b
16624        c
16625        "#
16626    .unindent();
16627
16628    cx.set_state(
16629        &r#"
16630        ˇA
16631        b
16632        C
16633        "#
16634        .unindent(),
16635    );
16636    cx.set_head_text(&diff_base);
16637    cx.update_editor(|editor, window, cx| {
16638        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16639    });
16640    executor.run_until_parked();
16641
16642    let both_hunks_expanded = r#"
16643        - a
16644        + ˇA
16645          b
16646        - c
16647        + C
16648        "#
16649    .unindent();
16650
16651    cx.assert_state_with_diff(both_hunks_expanded.clone());
16652
16653    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16654        let snapshot = editor.snapshot(window, cx);
16655        let hunks = editor
16656            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16657            .collect::<Vec<_>>();
16658        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16659        let buffer_id = hunks[0].buffer_id;
16660        hunks
16661            .into_iter()
16662            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16663            .collect::<Vec<_>>()
16664    });
16665    assert_eq!(hunk_ranges.len(), 2);
16666
16667    cx.update_editor(|editor, _, cx| {
16668        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16669    });
16670    executor.run_until_parked();
16671
16672    let second_hunk_expanded = r#"
16673          ˇA
16674          b
16675        - c
16676        + C
16677        "#
16678    .unindent();
16679
16680    cx.assert_state_with_diff(second_hunk_expanded);
16681
16682    cx.update_editor(|editor, _, cx| {
16683        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16684    });
16685    executor.run_until_parked();
16686
16687    cx.assert_state_with_diff(both_hunks_expanded.clone());
16688
16689    cx.update_editor(|editor, _, cx| {
16690        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16691    });
16692    executor.run_until_parked();
16693
16694    let first_hunk_expanded = r#"
16695        - a
16696        + ˇA
16697          b
16698          C
16699        "#
16700    .unindent();
16701
16702    cx.assert_state_with_diff(first_hunk_expanded);
16703
16704    cx.update_editor(|editor, _, cx| {
16705        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16706    });
16707    executor.run_until_parked();
16708
16709    cx.assert_state_with_diff(both_hunks_expanded);
16710
16711    cx.set_state(
16712        &r#"
16713        ˇA
16714        b
16715        "#
16716        .unindent(),
16717    );
16718    cx.run_until_parked();
16719
16720    // TODO this cursor position seems bad
16721    cx.assert_state_with_diff(
16722        r#"
16723        - ˇa
16724        + A
16725          b
16726        "#
16727        .unindent(),
16728    );
16729
16730    cx.update_editor(|editor, window, cx| {
16731        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16732    });
16733
16734    cx.assert_state_with_diff(
16735        r#"
16736            - ˇa
16737            + A
16738              b
16739            - c
16740            "#
16741        .unindent(),
16742    );
16743
16744    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16745        let snapshot = editor.snapshot(window, cx);
16746        let hunks = editor
16747            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16748            .collect::<Vec<_>>();
16749        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16750        let buffer_id = hunks[0].buffer_id;
16751        hunks
16752            .into_iter()
16753            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16754            .collect::<Vec<_>>()
16755    });
16756    assert_eq!(hunk_ranges.len(), 2);
16757
16758    cx.update_editor(|editor, _, cx| {
16759        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16760    });
16761    executor.run_until_parked();
16762
16763    cx.assert_state_with_diff(
16764        r#"
16765        - ˇa
16766        + A
16767          b
16768        "#
16769        .unindent(),
16770    );
16771}
16772
16773#[gpui::test]
16774async fn test_toggle_deletion_hunk_at_start_of_file(
16775    executor: BackgroundExecutor,
16776    cx: &mut TestAppContext,
16777) {
16778    init_test(cx, |_| {});
16779    let mut cx = EditorTestContext::new(cx).await;
16780
16781    let diff_base = r#"
16782        a
16783        b
16784        c
16785        "#
16786    .unindent();
16787
16788    cx.set_state(
16789        &r#"
16790        ˇb
16791        c
16792        "#
16793        .unindent(),
16794    );
16795    cx.set_head_text(&diff_base);
16796    cx.update_editor(|editor, window, cx| {
16797        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16798    });
16799    executor.run_until_parked();
16800
16801    let hunk_expanded = r#"
16802        - a
16803          ˇb
16804          c
16805        "#
16806    .unindent();
16807
16808    cx.assert_state_with_diff(hunk_expanded.clone());
16809
16810    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16811        let snapshot = editor.snapshot(window, cx);
16812        let hunks = editor
16813            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16814            .collect::<Vec<_>>();
16815        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16816        let buffer_id = hunks[0].buffer_id;
16817        hunks
16818            .into_iter()
16819            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16820            .collect::<Vec<_>>()
16821    });
16822    assert_eq!(hunk_ranges.len(), 1);
16823
16824    cx.update_editor(|editor, _, cx| {
16825        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16826    });
16827    executor.run_until_parked();
16828
16829    let hunk_collapsed = r#"
16830          ˇb
16831          c
16832        "#
16833    .unindent();
16834
16835    cx.assert_state_with_diff(hunk_collapsed);
16836
16837    cx.update_editor(|editor, _, cx| {
16838        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16839    });
16840    executor.run_until_parked();
16841
16842    cx.assert_state_with_diff(hunk_expanded.clone());
16843}
16844
16845#[gpui::test]
16846async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16847    init_test(cx, |_| {});
16848
16849    let fs = FakeFs::new(cx.executor());
16850    fs.insert_tree(
16851        path!("/test"),
16852        json!({
16853            ".git": {},
16854            "file-1": "ONE\n",
16855            "file-2": "TWO\n",
16856            "file-3": "THREE\n",
16857        }),
16858    )
16859    .await;
16860
16861    fs.set_head_for_repo(
16862        path!("/test/.git").as_ref(),
16863        &[
16864            ("file-1".into(), "one\n".into()),
16865            ("file-2".into(), "two\n".into()),
16866            ("file-3".into(), "three\n".into()),
16867        ],
16868    );
16869
16870    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16871    let mut buffers = vec![];
16872    for i in 1..=3 {
16873        let buffer = project
16874            .update(cx, |project, cx| {
16875                let path = format!(path!("/test/file-{}"), i);
16876                project.open_local_buffer(path, cx)
16877            })
16878            .await
16879            .unwrap();
16880        buffers.push(buffer);
16881    }
16882
16883    let multibuffer = cx.new(|cx| {
16884        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16885        multibuffer.set_all_diff_hunks_expanded(cx);
16886        for buffer in &buffers {
16887            let snapshot = buffer.read(cx).snapshot();
16888            multibuffer.set_excerpts_for_path(
16889                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16890                buffer.clone(),
16891                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16892                DEFAULT_MULTIBUFFER_CONTEXT,
16893                cx,
16894            );
16895        }
16896        multibuffer
16897    });
16898
16899    let editor = cx.add_window(|window, cx| {
16900        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
16901    });
16902    cx.run_until_parked();
16903
16904    let snapshot = editor
16905        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16906        .unwrap();
16907    let hunks = snapshot
16908        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16909        .map(|hunk| match hunk {
16910            DisplayDiffHunk::Unfolded {
16911                display_row_range, ..
16912            } => display_row_range,
16913            DisplayDiffHunk::Folded { .. } => unreachable!(),
16914        })
16915        .collect::<Vec<_>>();
16916    assert_eq!(
16917        hunks,
16918        [
16919            DisplayRow(2)..DisplayRow(4),
16920            DisplayRow(7)..DisplayRow(9),
16921            DisplayRow(12)..DisplayRow(14),
16922        ]
16923    );
16924}
16925
16926#[gpui::test]
16927async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16928    init_test(cx, |_| {});
16929
16930    let mut cx = EditorTestContext::new(cx).await;
16931    cx.set_head_text(indoc! { "
16932        one
16933        two
16934        three
16935        four
16936        five
16937        "
16938    });
16939    cx.set_index_text(indoc! { "
16940        one
16941        two
16942        three
16943        four
16944        five
16945        "
16946    });
16947    cx.set_state(indoc! {"
16948        one
16949        TWO
16950        ˇTHREE
16951        FOUR
16952        five
16953    "});
16954    cx.run_until_parked();
16955    cx.update_editor(|editor, window, cx| {
16956        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16957    });
16958    cx.run_until_parked();
16959    cx.assert_index_text(Some(indoc! {"
16960        one
16961        TWO
16962        THREE
16963        FOUR
16964        five
16965    "}));
16966    cx.set_state(indoc! { "
16967        one
16968        TWO
16969        ˇTHREE-HUNDRED
16970        FOUR
16971        five
16972    "});
16973    cx.run_until_parked();
16974    cx.update_editor(|editor, window, cx| {
16975        let snapshot = editor.snapshot(window, cx);
16976        let hunks = editor
16977            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16978            .collect::<Vec<_>>();
16979        assert_eq!(hunks.len(), 1);
16980        assert_eq!(
16981            hunks[0].status(),
16982            DiffHunkStatus {
16983                kind: DiffHunkStatusKind::Modified,
16984                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16985            }
16986        );
16987
16988        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16989    });
16990    cx.run_until_parked();
16991    cx.assert_index_text(Some(indoc! {"
16992        one
16993        TWO
16994        THREE-HUNDRED
16995        FOUR
16996        five
16997    "}));
16998}
16999
17000#[gpui::test]
17001fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17002    init_test(cx, |_| {});
17003
17004    let editor = cx.add_window(|window, cx| {
17005        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17006        build_editor(buffer, window, cx)
17007    });
17008
17009    let render_args = Arc::new(Mutex::new(None));
17010    let snapshot = editor
17011        .update(cx, |editor, window, cx| {
17012            let snapshot = editor.buffer().read(cx).snapshot(cx);
17013            let range =
17014                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17015
17016            struct RenderArgs {
17017                row: MultiBufferRow,
17018                folded: bool,
17019                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17020            }
17021
17022            let crease = Crease::inline(
17023                range,
17024                FoldPlaceholder::test(),
17025                {
17026                    let toggle_callback = render_args.clone();
17027                    move |row, folded, callback, _window, _cx| {
17028                        *toggle_callback.lock() = Some(RenderArgs {
17029                            row,
17030                            folded,
17031                            callback,
17032                        });
17033                        div()
17034                    }
17035                },
17036                |_row, _folded, _window, _cx| div(),
17037            );
17038
17039            editor.insert_creases(Some(crease), cx);
17040            let snapshot = editor.snapshot(window, cx);
17041            let _div = snapshot.render_crease_toggle(
17042                MultiBufferRow(1),
17043                false,
17044                cx.entity().clone(),
17045                window,
17046                cx,
17047            );
17048            snapshot
17049        })
17050        .unwrap();
17051
17052    let render_args = render_args.lock().take().unwrap();
17053    assert_eq!(render_args.row, MultiBufferRow(1));
17054    assert!(!render_args.folded);
17055    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17056
17057    cx.update_window(*editor, |_, window, cx| {
17058        (render_args.callback)(true, window, cx)
17059    })
17060    .unwrap();
17061    let snapshot = editor
17062        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17063        .unwrap();
17064    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17065
17066    cx.update_window(*editor, |_, window, cx| {
17067        (render_args.callback)(false, window, cx)
17068    })
17069    .unwrap();
17070    let snapshot = editor
17071        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17072        .unwrap();
17073    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17074}
17075
17076#[gpui::test]
17077async fn test_input_text(cx: &mut TestAppContext) {
17078    init_test(cx, |_| {});
17079    let mut cx = EditorTestContext::new(cx).await;
17080
17081    cx.set_state(
17082        &r#"ˇone
17083        two
17084
17085        three
17086        fourˇ
17087        five
17088
17089        siˇx"#
17090            .unindent(),
17091    );
17092
17093    cx.dispatch_action(HandleInput(String::new()));
17094    cx.assert_editor_state(
17095        &r#"ˇone
17096        two
17097
17098        three
17099        fourˇ
17100        five
17101
17102        siˇx"#
17103            .unindent(),
17104    );
17105
17106    cx.dispatch_action(HandleInput("AAAA".to_string()));
17107    cx.assert_editor_state(
17108        &r#"AAAAˇone
17109        two
17110
17111        three
17112        fourAAAAˇ
17113        five
17114
17115        siAAAAˇx"#
17116            .unindent(),
17117    );
17118}
17119
17120#[gpui::test]
17121async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17122    init_test(cx, |_| {});
17123
17124    let mut cx = EditorTestContext::new(cx).await;
17125    cx.set_state(
17126        r#"let foo = 1;
17127let foo = 2;
17128let foo = 3;
17129let fooˇ = 4;
17130let foo = 5;
17131let foo = 6;
17132let foo = 7;
17133let foo = 8;
17134let foo = 9;
17135let foo = 10;
17136let foo = 11;
17137let foo = 12;
17138let foo = 13;
17139let foo = 14;
17140let foo = 15;"#,
17141    );
17142
17143    cx.update_editor(|e, window, cx| {
17144        assert_eq!(
17145            e.next_scroll_position,
17146            NextScrollCursorCenterTopBottom::Center,
17147            "Default next scroll direction is center",
17148        );
17149
17150        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17151        assert_eq!(
17152            e.next_scroll_position,
17153            NextScrollCursorCenterTopBottom::Top,
17154            "After center, next scroll direction should be top",
17155        );
17156
17157        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17158        assert_eq!(
17159            e.next_scroll_position,
17160            NextScrollCursorCenterTopBottom::Bottom,
17161            "After top, next scroll direction should be bottom",
17162        );
17163
17164        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17165        assert_eq!(
17166            e.next_scroll_position,
17167            NextScrollCursorCenterTopBottom::Center,
17168            "After bottom, scrolling should start over",
17169        );
17170
17171        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17172        assert_eq!(
17173            e.next_scroll_position,
17174            NextScrollCursorCenterTopBottom::Top,
17175            "Scrolling continues if retriggered fast enough"
17176        );
17177    });
17178
17179    cx.executor()
17180        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17181    cx.executor().run_until_parked();
17182    cx.update_editor(|e, _, _| {
17183        assert_eq!(
17184            e.next_scroll_position,
17185            NextScrollCursorCenterTopBottom::Center,
17186            "If scrolling is not triggered fast enough, it should reset"
17187        );
17188    });
17189}
17190
17191#[gpui::test]
17192async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17193    init_test(cx, |_| {});
17194    let mut cx = EditorLspTestContext::new_rust(
17195        lsp::ServerCapabilities {
17196            definition_provider: Some(lsp::OneOf::Left(true)),
17197            references_provider: Some(lsp::OneOf::Left(true)),
17198            ..lsp::ServerCapabilities::default()
17199        },
17200        cx,
17201    )
17202    .await;
17203
17204    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17205        let go_to_definition = cx
17206            .lsp
17207            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17208                move |params, _| async move {
17209                    if empty_go_to_definition {
17210                        Ok(None)
17211                    } else {
17212                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17213                            uri: params.text_document_position_params.text_document.uri,
17214                            range: lsp::Range::new(
17215                                lsp::Position::new(4, 3),
17216                                lsp::Position::new(4, 6),
17217                            ),
17218                        })))
17219                    }
17220                },
17221            );
17222        let references = cx
17223            .lsp
17224            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17225                Ok(Some(vec![lsp::Location {
17226                    uri: params.text_document_position.text_document.uri,
17227                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17228                }]))
17229            });
17230        (go_to_definition, references)
17231    };
17232
17233    cx.set_state(
17234        &r#"fn one() {
17235            let mut a = ˇtwo();
17236        }
17237
17238        fn two() {}"#
17239            .unindent(),
17240    );
17241    set_up_lsp_handlers(false, &mut cx);
17242    let navigated = cx
17243        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17244        .await
17245        .expect("Failed to navigate to definition");
17246    assert_eq!(
17247        navigated,
17248        Navigated::Yes,
17249        "Should have navigated to definition from the GetDefinition response"
17250    );
17251    cx.assert_editor_state(
17252        &r#"fn one() {
17253            let mut a = two();
17254        }
17255
17256        fn «twoˇ»() {}"#
17257            .unindent(),
17258    );
17259
17260    let editors = cx.update_workspace(|workspace, _, cx| {
17261        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17262    });
17263    cx.update_editor(|_, _, test_editor_cx| {
17264        assert_eq!(
17265            editors.len(),
17266            1,
17267            "Initially, only one, test, editor should be open in the workspace"
17268        );
17269        assert_eq!(
17270            test_editor_cx.entity(),
17271            editors.last().expect("Asserted len is 1").clone()
17272        );
17273    });
17274
17275    set_up_lsp_handlers(true, &mut cx);
17276    let navigated = cx
17277        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17278        .await
17279        .expect("Failed to navigate to lookup references");
17280    assert_eq!(
17281        navigated,
17282        Navigated::Yes,
17283        "Should have navigated to references as a fallback after empty GoToDefinition response"
17284    );
17285    // We should not change the selections in the existing file,
17286    // if opening another milti buffer with the references
17287    cx.assert_editor_state(
17288        &r#"fn one() {
17289            let mut a = two();
17290        }
17291
17292        fn «twoˇ»() {}"#
17293            .unindent(),
17294    );
17295    let editors = cx.update_workspace(|workspace, _, cx| {
17296        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17297    });
17298    cx.update_editor(|_, _, test_editor_cx| {
17299        assert_eq!(
17300            editors.len(),
17301            2,
17302            "After falling back to references search, we open a new editor with the results"
17303        );
17304        let references_fallback_text = editors
17305            .into_iter()
17306            .find(|new_editor| *new_editor != test_editor_cx.entity())
17307            .expect("Should have one non-test editor now")
17308            .read(test_editor_cx)
17309            .text(test_editor_cx);
17310        assert_eq!(
17311            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17312            "Should use the range from the references response and not the GoToDefinition one"
17313        );
17314    });
17315}
17316
17317#[gpui::test]
17318async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17319    init_test(cx, |_| {});
17320    cx.update(|cx| {
17321        let mut editor_settings = EditorSettings::get_global(cx).clone();
17322        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17323        EditorSettings::override_global(editor_settings, cx);
17324    });
17325    let mut cx = EditorLspTestContext::new_rust(
17326        lsp::ServerCapabilities {
17327            definition_provider: Some(lsp::OneOf::Left(true)),
17328            references_provider: Some(lsp::OneOf::Left(true)),
17329            ..lsp::ServerCapabilities::default()
17330        },
17331        cx,
17332    )
17333    .await;
17334    let original_state = r#"fn one() {
17335        let mut a = ˇtwo();
17336    }
17337
17338    fn two() {}"#
17339        .unindent();
17340    cx.set_state(&original_state);
17341
17342    let mut go_to_definition = cx
17343        .lsp
17344        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17345            move |_, _| async move { Ok(None) },
17346        );
17347    let _references = cx
17348        .lsp
17349        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17350            panic!("Should not call for references with no go to definition fallback")
17351        });
17352
17353    let navigated = cx
17354        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17355        .await
17356        .expect("Failed to navigate to lookup references");
17357    go_to_definition
17358        .next()
17359        .await
17360        .expect("Should have called the go_to_definition handler");
17361
17362    assert_eq!(
17363        navigated,
17364        Navigated::No,
17365        "Should have navigated to references as a fallback after empty GoToDefinition response"
17366    );
17367    cx.assert_editor_state(&original_state);
17368    let editors = cx.update_workspace(|workspace, _, cx| {
17369        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17370    });
17371    cx.update_editor(|_, _, _| {
17372        assert_eq!(
17373            editors.len(),
17374            1,
17375            "After unsuccessful fallback, no other editor should have been opened"
17376        );
17377    });
17378}
17379
17380#[gpui::test]
17381async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17382    init_test(cx, |_| {});
17383
17384    let language = Arc::new(Language::new(
17385        LanguageConfig::default(),
17386        Some(tree_sitter_rust::LANGUAGE.into()),
17387    ));
17388
17389    let text = r#"
17390        #[cfg(test)]
17391        mod tests() {
17392            #[test]
17393            fn runnable_1() {
17394                let a = 1;
17395            }
17396
17397            #[test]
17398            fn runnable_2() {
17399                let a = 1;
17400                let b = 2;
17401            }
17402        }
17403    "#
17404    .unindent();
17405
17406    let fs = FakeFs::new(cx.executor());
17407    fs.insert_file("/file.rs", Default::default()).await;
17408
17409    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17410    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17411    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17412    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17413    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17414
17415    let editor = cx.new_window_entity(|window, cx| {
17416        Editor::new(
17417            EditorMode::full(),
17418            multi_buffer,
17419            Some(project.clone()),
17420            window,
17421            cx,
17422        )
17423    });
17424
17425    editor.update_in(cx, |editor, window, cx| {
17426        let snapshot = editor.buffer().read(cx).snapshot(cx);
17427        editor.tasks.insert(
17428            (buffer.read(cx).remote_id(), 3),
17429            RunnableTasks {
17430                templates: vec![],
17431                offset: snapshot.anchor_before(43),
17432                column: 0,
17433                extra_variables: HashMap::default(),
17434                context_range: BufferOffset(43)..BufferOffset(85),
17435            },
17436        );
17437        editor.tasks.insert(
17438            (buffer.read(cx).remote_id(), 8),
17439            RunnableTasks {
17440                templates: vec![],
17441                offset: snapshot.anchor_before(86),
17442                column: 0,
17443                extra_variables: HashMap::default(),
17444                context_range: BufferOffset(86)..BufferOffset(191),
17445            },
17446        );
17447
17448        // Test finding task when cursor is inside function body
17449        editor.change_selections(None, window, cx, |s| {
17450            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17451        });
17452        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17453        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17454
17455        // Test finding task when cursor is on function name
17456        editor.change_selections(None, window, cx, |s| {
17457            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17458        });
17459        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17460        assert_eq!(row, 8, "Should find task when cursor is on function name");
17461    });
17462}
17463
17464#[gpui::test]
17465async fn test_folding_buffers(cx: &mut TestAppContext) {
17466    init_test(cx, |_| {});
17467
17468    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17469    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17470    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17471
17472    let fs = FakeFs::new(cx.executor());
17473    fs.insert_tree(
17474        path!("/a"),
17475        json!({
17476            "first.rs": sample_text_1,
17477            "second.rs": sample_text_2,
17478            "third.rs": sample_text_3,
17479        }),
17480    )
17481    .await;
17482    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17483    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17484    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17485    let worktree = project.update(cx, |project, cx| {
17486        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17487        assert_eq!(worktrees.len(), 1);
17488        worktrees.pop().unwrap()
17489    });
17490    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17491
17492    let buffer_1 = project
17493        .update(cx, |project, cx| {
17494            project.open_buffer((worktree_id, "first.rs"), cx)
17495        })
17496        .await
17497        .unwrap();
17498    let buffer_2 = project
17499        .update(cx, |project, cx| {
17500            project.open_buffer((worktree_id, "second.rs"), cx)
17501        })
17502        .await
17503        .unwrap();
17504    let buffer_3 = project
17505        .update(cx, |project, cx| {
17506            project.open_buffer((worktree_id, "third.rs"), cx)
17507        })
17508        .await
17509        .unwrap();
17510
17511    let multi_buffer = cx.new(|cx| {
17512        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17513        multi_buffer.push_excerpts(
17514            buffer_1.clone(),
17515            [
17516                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17517                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17518                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17519            ],
17520            cx,
17521        );
17522        multi_buffer.push_excerpts(
17523            buffer_2.clone(),
17524            [
17525                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17526                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17527                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17528            ],
17529            cx,
17530        );
17531        multi_buffer.push_excerpts(
17532            buffer_3.clone(),
17533            [
17534                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17535                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17536                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17537            ],
17538            cx,
17539        );
17540        multi_buffer
17541    });
17542    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17543        Editor::new(
17544            EditorMode::full(),
17545            multi_buffer.clone(),
17546            Some(project.clone()),
17547            window,
17548            cx,
17549        )
17550    });
17551
17552    assert_eq!(
17553        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17554        "\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",
17555    );
17556
17557    multi_buffer_editor.update(cx, |editor, cx| {
17558        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17559    });
17560    assert_eq!(
17561        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17562        "\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",
17563        "After folding the first buffer, its text should not be displayed"
17564    );
17565
17566    multi_buffer_editor.update(cx, |editor, cx| {
17567        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17568    });
17569    assert_eq!(
17570        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17571        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17572        "After folding the second buffer, its text should not be displayed"
17573    );
17574
17575    multi_buffer_editor.update(cx, |editor, cx| {
17576        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17577    });
17578    assert_eq!(
17579        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17580        "\n\n\n\n\n",
17581        "After folding the third buffer, its text should not be displayed"
17582    );
17583
17584    // Emulate selection inside the fold logic, that should work
17585    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17586        editor
17587            .snapshot(window, cx)
17588            .next_line_boundary(Point::new(0, 4));
17589    });
17590
17591    multi_buffer_editor.update(cx, |editor, cx| {
17592        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17593    });
17594    assert_eq!(
17595        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17596        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17597        "After unfolding the second buffer, its text should be displayed"
17598    );
17599
17600    // Typing inside of buffer 1 causes that buffer to be unfolded.
17601    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17602        assert_eq!(
17603            multi_buffer
17604                .read(cx)
17605                .snapshot(cx)
17606                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17607                .collect::<String>(),
17608            "bbbb"
17609        );
17610        editor.change_selections(None, window, cx, |selections| {
17611            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17612        });
17613        editor.handle_input("B", window, cx);
17614    });
17615
17616    assert_eq!(
17617        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17618        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17619        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17620    );
17621
17622    multi_buffer_editor.update(cx, |editor, cx| {
17623        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17624    });
17625    assert_eq!(
17626        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17627        "\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",
17628        "After unfolding the all buffers, all original text should be displayed"
17629    );
17630}
17631
17632#[gpui::test]
17633async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17634    init_test(cx, |_| {});
17635
17636    let sample_text_1 = "1111\n2222\n3333".to_string();
17637    let sample_text_2 = "4444\n5555\n6666".to_string();
17638    let sample_text_3 = "7777\n8888\n9999".to_string();
17639
17640    let fs = FakeFs::new(cx.executor());
17641    fs.insert_tree(
17642        path!("/a"),
17643        json!({
17644            "first.rs": sample_text_1,
17645            "second.rs": sample_text_2,
17646            "third.rs": sample_text_3,
17647        }),
17648    )
17649    .await;
17650    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17651    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17652    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17653    let worktree = project.update(cx, |project, cx| {
17654        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17655        assert_eq!(worktrees.len(), 1);
17656        worktrees.pop().unwrap()
17657    });
17658    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17659
17660    let buffer_1 = project
17661        .update(cx, |project, cx| {
17662            project.open_buffer((worktree_id, "first.rs"), cx)
17663        })
17664        .await
17665        .unwrap();
17666    let buffer_2 = project
17667        .update(cx, |project, cx| {
17668            project.open_buffer((worktree_id, "second.rs"), cx)
17669        })
17670        .await
17671        .unwrap();
17672    let buffer_3 = project
17673        .update(cx, |project, cx| {
17674            project.open_buffer((worktree_id, "third.rs"), cx)
17675        })
17676        .await
17677        .unwrap();
17678
17679    let multi_buffer = cx.new(|cx| {
17680        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17681        multi_buffer.push_excerpts(
17682            buffer_1.clone(),
17683            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17684            cx,
17685        );
17686        multi_buffer.push_excerpts(
17687            buffer_2.clone(),
17688            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17689            cx,
17690        );
17691        multi_buffer.push_excerpts(
17692            buffer_3.clone(),
17693            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17694            cx,
17695        );
17696        multi_buffer
17697    });
17698
17699    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17700        Editor::new(
17701            EditorMode::full(),
17702            multi_buffer,
17703            Some(project.clone()),
17704            window,
17705            cx,
17706        )
17707    });
17708
17709    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17710    assert_eq!(
17711        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17712        full_text,
17713    );
17714
17715    multi_buffer_editor.update(cx, |editor, cx| {
17716        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17717    });
17718    assert_eq!(
17719        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17720        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17721        "After folding the first buffer, its text should not be displayed"
17722    );
17723
17724    multi_buffer_editor.update(cx, |editor, cx| {
17725        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17726    });
17727
17728    assert_eq!(
17729        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17730        "\n\n\n\n\n\n7777\n8888\n9999",
17731        "After folding the second buffer, its text should not be displayed"
17732    );
17733
17734    multi_buffer_editor.update(cx, |editor, cx| {
17735        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17736    });
17737    assert_eq!(
17738        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17739        "\n\n\n\n\n",
17740        "After folding the third buffer, its text should not be displayed"
17741    );
17742
17743    multi_buffer_editor.update(cx, |editor, cx| {
17744        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17745    });
17746    assert_eq!(
17747        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17748        "\n\n\n\n4444\n5555\n6666\n\n",
17749        "After unfolding the second buffer, its text should be displayed"
17750    );
17751
17752    multi_buffer_editor.update(cx, |editor, cx| {
17753        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17754    });
17755    assert_eq!(
17756        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17757        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17758        "After unfolding the first buffer, its text should be displayed"
17759    );
17760
17761    multi_buffer_editor.update(cx, |editor, cx| {
17762        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17763    });
17764    assert_eq!(
17765        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17766        full_text,
17767        "After unfolding all buffers, all original text should be displayed"
17768    );
17769}
17770
17771#[gpui::test]
17772async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17773    init_test(cx, |_| {});
17774
17775    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17776
17777    let fs = FakeFs::new(cx.executor());
17778    fs.insert_tree(
17779        path!("/a"),
17780        json!({
17781            "main.rs": sample_text,
17782        }),
17783    )
17784    .await;
17785    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17786    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17787    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17788    let worktree = project.update(cx, |project, cx| {
17789        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17790        assert_eq!(worktrees.len(), 1);
17791        worktrees.pop().unwrap()
17792    });
17793    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17794
17795    let buffer_1 = project
17796        .update(cx, |project, cx| {
17797            project.open_buffer((worktree_id, "main.rs"), cx)
17798        })
17799        .await
17800        .unwrap();
17801
17802    let multi_buffer = cx.new(|cx| {
17803        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17804        multi_buffer.push_excerpts(
17805            buffer_1.clone(),
17806            [ExcerptRange::new(
17807                Point::new(0, 0)
17808                    ..Point::new(
17809                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17810                        0,
17811                    ),
17812            )],
17813            cx,
17814        );
17815        multi_buffer
17816    });
17817    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17818        Editor::new(
17819            EditorMode::full(),
17820            multi_buffer,
17821            Some(project.clone()),
17822            window,
17823            cx,
17824        )
17825    });
17826
17827    let selection_range = Point::new(1, 0)..Point::new(2, 0);
17828    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17829        enum TestHighlight {}
17830        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17831        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17832        editor.highlight_text::<TestHighlight>(
17833            vec![highlight_range.clone()],
17834            HighlightStyle::color(Hsla::green()),
17835            cx,
17836        );
17837        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17838    });
17839
17840    let full_text = format!("\n\n{sample_text}");
17841    assert_eq!(
17842        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17843        full_text,
17844    );
17845}
17846
17847#[gpui::test]
17848async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17849    init_test(cx, |_| {});
17850    cx.update(|cx| {
17851        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17852            "keymaps/default-linux.json",
17853            cx,
17854        )
17855        .unwrap();
17856        cx.bind_keys(default_key_bindings);
17857    });
17858
17859    let (editor, cx) = cx.add_window_view(|window, cx| {
17860        let multi_buffer = MultiBuffer::build_multi(
17861            [
17862                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17863                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17864                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17865                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17866            ],
17867            cx,
17868        );
17869        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
17870
17871        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17872        // fold all but the second buffer, so that we test navigating between two
17873        // adjacent folded buffers, as well as folded buffers at the start and
17874        // end the multibuffer
17875        editor.fold_buffer(buffer_ids[0], cx);
17876        editor.fold_buffer(buffer_ids[2], cx);
17877        editor.fold_buffer(buffer_ids[3], cx);
17878
17879        editor
17880    });
17881    cx.simulate_resize(size(px(1000.), px(1000.)));
17882
17883    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17884    cx.assert_excerpts_with_selections(indoc! {"
17885        [EXCERPT]
17886        ˇ[FOLDED]
17887        [EXCERPT]
17888        a1
17889        b1
17890        [EXCERPT]
17891        [FOLDED]
17892        [EXCERPT]
17893        [FOLDED]
17894        "
17895    });
17896    cx.simulate_keystroke("down");
17897    cx.assert_excerpts_with_selections(indoc! {"
17898        [EXCERPT]
17899        [FOLDED]
17900        [EXCERPT]
17901        ˇa1
17902        b1
17903        [EXCERPT]
17904        [FOLDED]
17905        [EXCERPT]
17906        [FOLDED]
17907        "
17908    });
17909    cx.simulate_keystroke("down");
17910    cx.assert_excerpts_with_selections(indoc! {"
17911        [EXCERPT]
17912        [FOLDED]
17913        [EXCERPT]
17914        a1
17915        ˇb1
17916        [EXCERPT]
17917        [FOLDED]
17918        [EXCERPT]
17919        [FOLDED]
17920        "
17921    });
17922    cx.simulate_keystroke("down");
17923    cx.assert_excerpts_with_selections(indoc! {"
17924        [EXCERPT]
17925        [FOLDED]
17926        [EXCERPT]
17927        a1
17928        b1
17929        ˇ[EXCERPT]
17930        [FOLDED]
17931        [EXCERPT]
17932        [FOLDED]
17933        "
17934    });
17935    cx.simulate_keystroke("down");
17936    cx.assert_excerpts_with_selections(indoc! {"
17937        [EXCERPT]
17938        [FOLDED]
17939        [EXCERPT]
17940        a1
17941        b1
17942        [EXCERPT]
17943        ˇ[FOLDED]
17944        [EXCERPT]
17945        [FOLDED]
17946        "
17947    });
17948    for _ in 0..5 {
17949        cx.simulate_keystroke("down");
17950        cx.assert_excerpts_with_selections(indoc! {"
17951            [EXCERPT]
17952            [FOLDED]
17953            [EXCERPT]
17954            a1
17955            b1
17956            [EXCERPT]
17957            [FOLDED]
17958            [EXCERPT]
17959            ˇ[FOLDED]
17960            "
17961        });
17962    }
17963
17964    cx.simulate_keystroke("up");
17965    cx.assert_excerpts_with_selections(indoc! {"
17966        [EXCERPT]
17967        [FOLDED]
17968        [EXCERPT]
17969        a1
17970        b1
17971        [EXCERPT]
17972        ˇ[FOLDED]
17973        [EXCERPT]
17974        [FOLDED]
17975        "
17976    });
17977    cx.simulate_keystroke("up");
17978    cx.assert_excerpts_with_selections(indoc! {"
17979        [EXCERPT]
17980        [FOLDED]
17981        [EXCERPT]
17982        a1
17983        b1
17984        ˇ[EXCERPT]
17985        [FOLDED]
17986        [EXCERPT]
17987        [FOLDED]
17988        "
17989    });
17990    cx.simulate_keystroke("up");
17991    cx.assert_excerpts_with_selections(indoc! {"
17992        [EXCERPT]
17993        [FOLDED]
17994        [EXCERPT]
17995        a1
17996        ˇb1
17997        [EXCERPT]
17998        [FOLDED]
17999        [EXCERPT]
18000        [FOLDED]
18001        "
18002    });
18003    cx.simulate_keystroke("up");
18004    cx.assert_excerpts_with_selections(indoc! {"
18005        [EXCERPT]
18006        [FOLDED]
18007        [EXCERPT]
18008        ˇa1
18009        b1
18010        [EXCERPT]
18011        [FOLDED]
18012        [EXCERPT]
18013        [FOLDED]
18014        "
18015    });
18016    for _ in 0..5 {
18017        cx.simulate_keystroke("up");
18018        cx.assert_excerpts_with_selections(indoc! {"
18019            [EXCERPT]
18020            ˇ[FOLDED]
18021            [EXCERPT]
18022            a1
18023            b1
18024            [EXCERPT]
18025            [FOLDED]
18026            [EXCERPT]
18027            [FOLDED]
18028            "
18029        });
18030    }
18031}
18032
18033#[gpui::test]
18034async fn test_inline_completion_text(cx: &mut TestAppContext) {
18035    init_test(cx, |_| {});
18036
18037    // Simple insertion
18038    assert_highlighted_edits(
18039        "Hello, world!",
18040        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18041        true,
18042        cx,
18043        |highlighted_edits, cx| {
18044            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18045            assert_eq!(highlighted_edits.highlights.len(), 1);
18046            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18047            assert_eq!(
18048                highlighted_edits.highlights[0].1.background_color,
18049                Some(cx.theme().status().created_background)
18050            );
18051        },
18052    )
18053    .await;
18054
18055    // Replacement
18056    assert_highlighted_edits(
18057        "This is a test.",
18058        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18059        false,
18060        cx,
18061        |highlighted_edits, cx| {
18062            assert_eq!(highlighted_edits.text, "That is a test.");
18063            assert_eq!(highlighted_edits.highlights.len(), 1);
18064            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18065            assert_eq!(
18066                highlighted_edits.highlights[0].1.background_color,
18067                Some(cx.theme().status().created_background)
18068            );
18069        },
18070    )
18071    .await;
18072
18073    // Multiple edits
18074    assert_highlighted_edits(
18075        "Hello, world!",
18076        vec![
18077            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18078            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18079        ],
18080        false,
18081        cx,
18082        |highlighted_edits, cx| {
18083            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18084            assert_eq!(highlighted_edits.highlights.len(), 2);
18085            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18086            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18087            assert_eq!(
18088                highlighted_edits.highlights[0].1.background_color,
18089                Some(cx.theme().status().created_background)
18090            );
18091            assert_eq!(
18092                highlighted_edits.highlights[1].1.background_color,
18093                Some(cx.theme().status().created_background)
18094            );
18095        },
18096    )
18097    .await;
18098
18099    // Multiple lines with edits
18100    assert_highlighted_edits(
18101        "First line\nSecond line\nThird line\nFourth line",
18102        vec![
18103            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18104            (
18105                Point::new(2, 0)..Point::new(2, 10),
18106                "New third line".to_string(),
18107            ),
18108            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18109        ],
18110        false,
18111        cx,
18112        |highlighted_edits, cx| {
18113            assert_eq!(
18114                highlighted_edits.text,
18115                "Second modified\nNew third line\nFourth updated line"
18116            );
18117            assert_eq!(highlighted_edits.highlights.len(), 3);
18118            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18119            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18120            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18121            for highlight in &highlighted_edits.highlights {
18122                assert_eq!(
18123                    highlight.1.background_color,
18124                    Some(cx.theme().status().created_background)
18125                );
18126            }
18127        },
18128    )
18129    .await;
18130}
18131
18132#[gpui::test]
18133async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18134    init_test(cx, |_| {});
18135
18136    // Deletion
18137    assert_highlighted_edits(
18138        "Hello, world!",
18139        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18140        true,
18141        cx,
18142        |highlighted_edits, cx| {
18143            assert_eq!(highlighted_edits.text, "Hello, world!");
18144            assert_eq!(highlighted_edits.highlights.len(), 1);
18145            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18146            assert_eq!(
18147                highlighted_edits.highlights[0].1.background_color,
18148                Some(cx.theme().status().deleted_background)
18149            );
18150        },
18151    )
18152    .await;
18153
18154    // Insertion
18155    assert_highlighted_edits(
18156        "Hello, world!",
18157        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18158        true,
18159        cx,
18160        |highlighted_edits, cx| {
18161            assert_eq!(highlighted_edits.highlights.len(), 1);
18162            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18163            assert_eq!(
18164                highlighted_edits.highlights[0].1.background_color,
18165                Some(cx.theme().status().created_background)
18166            );
18167        },
18168    )
18169    .await;
18170}
18171
18172async fn assert_highlighted_edits(
18173    text: &str,
18174    edits: Vec<(Range<Point>, String)>,
18175    include_deletions: bool,
18176    cx: &mut TestAppContext,
18177    assertion_fn: impl Fn(HighlightedText, &App),
18178) {
18179    let window = cx.add_window(|window, cx| {
18180        let buffer = MultiBuffer::build_simple(text, cx);
18181        Editor::new(EditorMode::full(), buffer, None, window, cx)
18182    });
18183    let cx = &mut VisualTestContext::from_window(*window, cx);
18184
18185    let (buffer, snapshot) = window
18186        .update(cx, |editor, _window, cx| {
18187            (
18188                editor.buffer().clone(),
18189                editor.buffer().read(cx).snapshot(cx),
18190            )
18191        })
18192        .unwrap();
18193
18194    let edits = edits
18195        .into_iter()
18196        .map(|(range, edit)| {
18197            (
18198                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18199                edit,
18200            )
18201        })
18202        .collect::<Vec<_>>();
18203
18204    let text_anchor_edits = edits
18205        .clone()
18206        .into_iter()
18207        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18208        .collect::<Vec<_>>();
18209
18210    let edit_preview = window
18211        .update(cx, |_, _window, cx| {
18212            buffer
18213                .read(cx)
18214                .as_singleton()
18215                .unwrap()
18216                .read(cx)
18217                .preview_edits(text_anchor_edits.into(), cx)
18218        })
18219        .unwrap()
18220        .await;
18221
18222    cx.update(|_window, cx| {
18223        let highlighted_edits = inline_completion_edit_text(
18224            &snapshot.as_singleton().unwrap().2,
18225            &edits,
18226            &edit_preview,
18227            include_deletions,
18228            cx,
18229        );
18230        assertion_fn(highlighted_edits, cx)
18231    });
18232}
18233
18234#[track_caller]
18235fn assert_breakpoint(
18236    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18237    path: &Arc<Path>,
18238    expected: Vec<(u32, Breakpoint)>,
18239) {
18240    if expected.len() == 0usize {
18241        assert!(!breakpoints.contains_key(path), "{}", path.display());
18242    } else {
18243        let mut breakpoint = breakpoints
18244            .get(path)
18245            .unwrap()
18246            .into_iter()
18247            .map(|breakpoint| {
18248                (
18249                    breakpoint.row,
18250                    Breakpoint {
18251                        message: breakpoint.message.clone(),
18252                        state: breakpoint.state,
18253                        condition: breakpoint.condition.clone(),
18254                        hit_condition: breakpoint.hit_condition.clone(),
18255                    },
18256                )
18257            })
18258            .collect::<Vec<_>>();
18259
18260        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18261
18262        assert_eq!(expected, breakpoint);
18263    }
18264}
18265
18266fn add_log_breakpoint_at_cursor(
18267    editor: &mut Editor,
18268    log_message: &str,
18269    window: &mut Window,
18270    cx: &mut Context<Editor>,
18271) {
18272    let (anchor, bp) = editor
18273        .breakpoints_at_cursors(window, cx)
18274        .first()
18275        .and_then(|(anchor, bp)| {
18276            if let Some(bp) = bp {
18277                Some((*anchor, bp.clone()))
18278            } else {
18279                None
18280            }
18281        })
18282        .unwrap_or_else(|| {
18283            let cursor_position: Point = editor.selections.newest(cx).head();
18284
18285            let breakpoint_position = editor
18286                .snapshot(window, cx)
18287                .display_snapshot
18288                .buffer_snapshot
18289                .anchor_before(Point::new(cursor_position.row, 0));
18290
18291            (breakpoint_position, Breakpoint::new_log(&log_message))
18292        });
18293
18294    editor.edit_breakpoint_at_anchor(
18295        anchor,
18296        bp,
18297        BreakpointEditAction::EditLogMessage(log_message.into()),
18298        cx,
18299    );
18300}
18301
18302#[gpui::test]
18303async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18304    init_test(cx, |_| {});
18305
18306    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18307    let fs = FakeFs::new(cx.executor());
18308    fs.insert_tree(
18309        path!("/a"),
18310        json!({
18311            "main.rs": sample_text,
18312        }),
18313    )
18314    .await;
18315    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18316    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18317    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18318
18319    let fs = FakeFs::new(cx.executor());
18320    fs.insert_tree(
18321        path!("/a"),
18322        json!({
18323            "main.rs": sample_text,
18324        }),
18325    )
18326    .await;
18327    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18328    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18329    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18330    let worktree_id = workspace
18331        .update(cx, |workspace, _window, cx| {
18332            workspace.project().update(cx, |project, cx| {
18333                project.worktrees(cx).next().unwrap().read(cx).id()
18334            })
18335        })
18336        .unwrap();
18337
18338    let buffer = project
18339        .update(cx, |project, cx| {
18340            project.open_buffer((worktree_id, "main.rs"), cx)
18341        })
18342        .await
18343        .unwrap();
18344
18345    let (editor, cx) = cx.add_window_view(|window, cx| {
18346        Editor::new(
18347            EditorMode::full(),
18348            MultiBuffer::build_from_buffer(buffer, cx),
18349            Some(project.clone()),
18350            window,
18351            cx,
18352        )
18353    });
18354
18355    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18356    let abs_path = project.read_with(cx, |project, cx| {
18357        project
18358            .absolute_path(&project_path, cx)
18359            .map(|path_buf| Arc::from(path_buf.to_owned()))
18360            .unwrap()
18361    });
18362
18363    // assert we can add breakpoint on the first line
18364    editor.update_in(cx, |editor, window, cx| {
18365        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18366        editor.move_to_end(&MoveToEnd, window, cx);
18367        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18368    });
18369
18370    let breakpoints = editor.update(cx, |editor, cx| {
18371        editor
18372            .breakpoint_store()
18373            .as_ref()
18374            .unwrap()
18375            .read(cx)
18376            .all_breakpoints(cx)
18377            .clone()
18378    });
18379
18380    assert_eq!(1, breakpoints.len());
18381    assert_breakpoint(
18382        &breakpoints,
18383        &abs_path,
18384        vec![
18385            (0, Breakpoint::new_standard()),
18386            (3, Breakpoint::new_standard()),
18387        ],
18388    );
18389
18390    editor.update_in(cx, |editor, window, cx| {
18391        editor.move_to_beginning(&MoveToBeginning, window, cx);
18392        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18393    });
18394
18395    let breakpoints = editor.update(cx, |editor, cx| {
18396        editor
18397            .breakpoint_store()
18398            .as_ref()
18399            .unwrap()
18400            .read(cx)
18401            .all_breakpoints(cx)
18402            .clone()
18403    });
18404
18405    assert_eq!(1, breakpoints.len());
18406    assert_breakpoint(
18407        &breakpoints,
18408        &abs_path,
18409        vec![(3, Breakpoint::new_standard())],
18410    );
18411
18412    editor.update_in(cx, |editor, window, cx| {
18413        editor.move_to_end(&MoveToEnd, window, cx);
18414        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18415    });
18416
18417    let breakpoints = editor.update(cx, |editor, cx| {
18418        editor
18419            .breakpoint_store()
18420            .as_ref()
18421            .unwrap()
18422            .read(cx)
18423            .all_breakpoints(cx)
18424            .clone()
18425    });
18426
18427    assert_eq!(0, breakpoints.len());
18428    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18429}
18430
18431#[gpui::test]
18432async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18433    init_test(cx, |_| {});
18434
18435    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18436
18437    let fs = FakeFs::new(cx.executor());
18438    fs.insert_tree(
18439        path!("/a"),
18440        json!({
18441            "main.rs": sample_text,
18442        }),
18443    )
18444    .await;
18445    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18446    let (workspace, cx) =
18447        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18448
18449    let worktree_id = workspace.update(cx, |workspace, cx| {
18450        workspace.project().update(cx, |project, cx| {
18451            project.worktrees(cx).next().unwrap().read(cx).id()
18452        })
18453    });
18454
18455    let buffer = project
18456        .update(cx, |project, cx| {
18457            project.open_buffer((worktree_id, "main.rs"), cx)
18458        })
18459        .await
18460        .unwrap();
18461
18462    let (editor, cx) = cx.add_window_view(|window, cx| {
18463        Editor::new(
18464            EditorMode::full(),
18465            MultiBuffer::build_from_buffer(buffer, cx),
18466            Some(project.clone()),
18467            window,
18468            cx,
18469        )
18470    });
18471
18472    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18473    let abs_path = project.read_with(cx, |project, cx| {
18474        project
18475            .absolute_path(&project_path, cx)
18476            .map(|path_buf| Arc::from(path_buf.to_owned()))
18477            .unwrap()
18478    });
18479
18480    editor.update_in(cx, |editor, window, cx| {
18481        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18482    });
18483
18484    let breakpoints = editor.update(cx, |editor, cx| {
18485        editor
18486            .breakpoint_store()
18487            .as_ref()
18488            .unwrap()
18489            .read(cx)
18490            .all_breakpoints(cx)
18491            .clone()
18492    });
18493
18494    assert_breakpoint(
18495        &breakpoints,
18496        &abs_path,
18497        vec![(0, Breakpoint::new_log("hello world"))],
18498    );
18499
18500    // Removing a log message from a log breakpoint should remove it
18501    editor.update_in(cx, |editor, window, cx| {
18502        add_log_breakpoint_at_cursor(editor, "", window, cx);
18503    });
18504
18505    let breakpoints = editor.update(cx, |editor, cx| {
18506        editor
18507            .breakpoint_store()
18508            .as_ref()
18509            .unwrap()
18510            .read(cx)
18511            .all_breakpoints(cx)
18512            .clone()
18513    });
18514
18515    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18516
18517    editor.update_in(cx, |editor, window, cx| {
18518        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18519        editor.move_to_end(&MoveToEnd, window, cx);
18520        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18521        // Not adding a log message to a standard breakpoint shouldn't remove it
18522        add_log_breakpoint_at_cursor(editor, "", window, cx);
18523    });
18524
18525    let breakpoints = editor.update(cx, |editor, cx| {
18526        editor
18527            .breakpoint_store()
18528            .as_ref()
18529            .unwrap()
18530            .read(cx)
18531            .all_breakpoints(cx)
18532            .clone()
18533    });
18534
18535    assert_breakpoint(
18536        &breakpoints,
18537        &abs_path,
18538        vec![
18539            (0, Breakpoint::new_standard()),
18540            (3, Breakpoint::new_standard()),
18541        ],
18542    );
18543
18544    editor.update_in(cx, |editor, window, cx| {
18545        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18546    });
18547
18548    let breakpoints = editor.update(cx, |editor, cx| {
18549        editor
18550            .breakpoint_store()
18551            .as_ref()
18552            .unwrap()
18553            .read(cx)
18554            .all_breakpoints(cx)
18555            .clone()
18556    });
18557
18558    assert_breakpoint(
18559        &breakpoints,
18560        &abs_path,
18561        vec![
18562            (0, Breakpoint::new_standard()),
18563            (3, Breakpoint::new_log("hello world")),
18564        ],
18565    );
18566
18567    editor.update_in(cx, |editor, window, cx| {
18568        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18569    });
18570
18571    let breakpoints = editor.update(cx, |editor, cx| {
18572        editor
18573            .breakpoint_store()
18574            .as_ref()
18575            .unwrap()
18576            .read(cx)
18577            .all_breakpoints(cx)
18578            .clone()
18579    });
18580
18581    assert_breakpoint(
18582        &breakpoints,
18583        &abs_path,
18584        vec![
18585            (0, Breakpoint::new_standard()),
18586            (3, Breakpoint::new_log("hello Earth!!")),
18587        ],
18588    );
18589}
18590
18591/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18592/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18593/// or when breakpoints were placed out of order. This tests for a regression too
18594#[gpui::test]
18595async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18596    init_test(cx, |_| {});
18597
18598    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18599    let fs = FakeFs::new(cx.executor());
18600    fs.insert_tree(
18601        path!("/a"),
18602        json!({
18603            "main.rs": sample_text,
18604        }),
18605    )
18606    .await;
18607    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18608    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18609    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18610
18611    let fs = FakeFs::new(cx.executor());
18612    fs.insert_tree(
18613        path!("/a"),
18614        json!({
18615            "main.rs": sample_text,
18616        }),
18617    )
18618    .await;
18619    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18620    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18621    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18622    let worktree_id = workspace
18623        .update(cx, |workspace, _window, cx| {
18624            workspace.project().update(cx, |project, cx| {
18625                project.worktrees(cx).next().unwrap().read(cx).id()
18626            })
18627        })
18628        .unwrap();
18629
18630    let buffer = project
18631        .update(cx, |project, cx| {
18632            project.open_buffer((worktree_id, "main.rs"), cx)
18633        })
18634        .await
18635        .unwrap();
18636
18637    let (editor, cx) = cx.add_window_view(|window, cx| {
18638        Editor::new(
18639            EditorMode::full(),
18640            MultiBuffer::build_from_buffer(buffer, cx),
18641            Some(project.clone()),
18642            window,
18643            cx,
18644        )
18645    });
18646
18647    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18648    let abs_path = project.read_with(cx, |project, cx| {
18649        project
18650            .absolute_path(&project_path, cx)
18651            .map(|path_buf| Arc::from(path_buf.to_owned()))
18652            .unwrap()
18653    });
18654
18655    // assert we can add breakpoint on the first line
18656    editor.update_in(cx, |editor, window, cx| {
18657        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18658        editor.move_to_end(&MoveToEnd, window, cx);
18659        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18660        editor.move_up(&MoveUp, window, cx);
18661        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18662    });
18663
18664    let breakpoints = editor.update(cx, |editor, cx| {
18665        editor
18666            .breakpoint_store()
18667            .as_ref()
18668            .unwrap()
18669            .read(cx)
18670            .all_breakpoints(cx)
18671            .clone()
18672    });
18673
18674    assert_eq!(1, breakpoints.len());
18675    assert_breakpoint(
18676        &breakpoints,
18677        &abs_path,
18678        vec![
18679            (0, Breakpoint::new_standard()),
18680            (2, Breakpoint::new_standard()),
18681            (3, Breakpoint::new_standard()),
18682        ],
18683    );
18684
18685    editor.update_in(cx, |editor, window, cx| {
18686        editor.move_to_beginning(&MoveToBeginning, window, cx);
18687        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18688        editor.move_to_end(&MoveToEnd, window, cx);
18689        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18690        // Disabling a breakpoint that doesn't exist should do nothing
18691        editor.move_up(&MoveUp, window, cx);
18692        editor.move_up(&MoveUp, window, cx);
18693        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18694    });
18695
18696    let breakpoints = editor.update(cx, |editor, cx| {
18697        editor
18698            .breakpoint_store()
18699            .as_ref()
18700            .unwrap()
18701            .read(cx)
18702            .all_breakpoints(cx)
18703            .clone()
18704    });
18705
18706    let disable_breakpoint = {
18707        let mut bp = Breakpoint::new_standard();
18708        bp.state = BreakpointState::Disabled;
18709        bp
18710    };
18711
18712    assert_eq!(1, breakpoints.len());
18713    assert_breakpoint(
18714        &breakpoints,
18715        &abs_path,
18716        vec![
18717            (0, disable_breakpoint.clone()),
18718            (2, Breakpoint::new_standard()),
18719            (3, disable_breakpoint.clone()),
18720        ],
18721    );
18722
18723    editor.update_in(cx, |editor, window, cx| {
18724        editor.move_to_beginning(&MoveToBeginning, window, cx);
18725        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18726        editor.move_to_end(&MoveToEnd, window, cx);
18727        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18728        editor.move_up(&MoveUp, window, cx);
18729        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18730    });
18731
18732    let breakpoints = editor.update(cx, |editor, cx| {
18733        editor
18734            .breakpoint_store()
18735            .as_ref()
18736            .unwrap()
18737            .read(cx)
18738            .all_breakpoints(cx)
18739            .clone()
18740    });
18741
18742    assert_eq!(1, breakpoints.len());
18743    assert_breakpoint(
18744        &breakpoints,
18745        &abs_path,
18746        vec![
18747            (0, Breakpoint::new_standard()),
18748            (2, disable_breakpoint),
18749            (3, Breakpoint::new_standard()),
18750        ],
18751    );
18752}
18753
18754#[gpui::test]
18755async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18756    init_test(cx, |_| {});
18757    let capabilities = lsp::ServerCapabilities {
18758        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18759            prepare_provider: Some(true),
18760            work_done_progress_options: Default::default(),
18761        })),
18762        ..Default::default()
18763    };
18764    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18765
18766    cx.set_state(indoc! {"
18767        struct Fˇoo {}
18768    "});
18769
18770    cx.update_editor(|editor, _, cx| {
18771        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18772        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18773        editor.highlight_background::<DocumentHighlightRead>(
18774            &[highlight_range],
18775            |c| c.editor_document_highlight_read_background,
18776            cx,
18777        );
18778    });
18779
18780    let mut prepare_rename_handler = cx
18781        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18782            move |_, _, _| async move {
18783                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18784                    start: lsp::Position {
18785                        line: 0,
18786                        character: 7,
18787                    },
18788                    end: lsp::Position {
18789                        line: 0,
18790                        character: 10,
18791                    },
18792                })))
18793            },
18794        );
18795    let prepare_rename_task = cx
18796        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18797        .expect("Prepare rename was not started");
18798    prepare_rename_handler.next().await.unwrap();
18799    prepare_rename_task.await.expect("Prepare rename failed");
18800
18801    let mut rename_handler =
18802        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18803            let edit = lsp::TextEdit {
18804                range: lsp::Range {
18805                    start: lsp::Position {
18806                        line: 0,
18807                        character: 7,
18808                    },
18809                    end: lsp::Position {
18810                        line: 0,
18811                        character: 10,
18812                    },
18813                },
18814                new_text: "FooRenamed".to_string(),
18815            };
18816            Ok(Some(lsp::WorkspaceEdit::new(
18817                // Specify the same edit twice
18818                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18819            )))
18820        });
18821    let rename_task = cx
18822        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18823        .expect("Confirm rename was not started");
18824    rename_handler.next().await.unwrap();
18825    rename_task.await.expect("Confirm rename failed");
18826    cx.run_until_parked();
18827
18828    // Despite two edits, only one is actually applied as those are identical
18829    cx.assert_editor_state(indoc! {"
18830        struct FooRenamedˇ {}
18831    "});
18832}
18833
18834#[gpui::test]
18835async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18836    init_test(cx, |_| {});
18837    // These capabilities indicate that the server does not support prepare rename.
18838    let capabilities = lsp::ServerCapabilities {
18839        rename_provider: Some(lsp::OneOf::Left(true)),
18840        ..Default::default()
18841    };
18842    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18843
18844    cx.set_state(indoc! {"
18845        struct Fˇoo {}
18846    "});
18847
18848    cx.update_editor(|editor, _window, cx| {
18849        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18850        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18851        editor.highlight_background::<DocumentHighlightRead>(
18852            &[highlight_range],
18853            |c| c.editor_document_highlight_read_background,
18854            cx,
18855        );
18856    });
18857
18858    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18859        .expect("Prepare rename was not started")
18860        .await
18861        .expect("Prepare rename failed");
18862
18863    let mut rename_handler =
18864        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18865            let edit = lsp::TextEdit {
18866                range: lsp::Range {
18867                    start: lsp::Position {
18868                        line: 0,
18869                        character: 7,
18870                    },
18871                    end: lsp::Position {
18872                        line: 0,
18873                        character: 10,
18874                    },
18875                },
18876                new_text: "FooRenamed".to_string(),
18877            };
18878            Ok(Some(lsp::WorkspaceEdit::new(
18879                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18880            )))
18881        });
18882    let rename_task = cx
18883        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18884        .expect("Confirm rename was not started");
18885    rename_handler.next().await.unwrap();
18886    rename_task.await.expect("Confirm rename failed");
18887    cx.run_until_parked();
18888
18889    // Correct range is renamed, as `surrounding_word` is used to find it.
18890    cx.assert_editor_state(indoc! {"
18891        struct FooRenamedˇ {}
18892    "});
18893}
18894
18895#[gpui::test]
18896async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18897    init_test(cx, |_| {});
18898    let mut cx = EditorTestContext::new(cx).await;
18899
18900    let language = Arc::new(
18901        Language::new(
18902            LanguageConfig::default(),
18903            Some(tree_sitter_html::LANGUAGE.into()),
18904        )
18905        .with_brackets_query(
18906            r#"
18907            ("<" @open "/>" @close)
18908            ("</" @open ">" @close)
18909            ("<" @open ">" @close)
18910            ("\"" @open "\"" @close)
18911            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18912        "#,
18913        )
18914        .unwrap(),
18915    );
18916    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18917
18918    cx.set_state(indoc! {"
18919        <span>ˇ</span>
18920    "});
18921    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18922    cx.assert_editor_state(indoc! {"
18923        <span>
18924        ˇ
18925        </span>
18926    "});
18927
18928    cx.set_state(indoc! {"
18929        <span><span></span>ˇ</span>
18930    "});
18931    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18932    cx.assert_editor_state(indoc! {"
18933        <span><span></span>
18934        ˇ</span>
18935    "});
18936
18937    cx.set_state(indoc! {"
18938        <span>ˇ
18939        </span>
18940    "});
18941    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18942    cx.assert_editor_state(indoc! {"
18943        <span>
18944        ˇ
18945        </span>
18946    "});
18947}
18948
18949#[gpui::test(iterations = 10)]
18950async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18951    init_test(cx, |_| {});
18952
18953    let fs = FakeFs::new(cx.executor());
18954    fs.insert_tree(
18955        path!("/dir"),
18956        json!({
18957            "a.ts": "a",
18958        }),
18959    )
18960    .await;
18961
18962    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18963    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18964    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18965
18966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18967    language_registry.add(Arc::new(Language::new(
18968        LanguageConfig {
18969            name: "TypeScript".into(),
18970            matcher: LanguageMatcher {
18971                path_suffixes: vec!["ts".to_string()],
18972                ..Default::default()
18973            },
18974            ..Default::default()
18975        },
18976        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18977    )));
18978    let mut fake_language_servers = language_registry.register_fake_lsp(
18979        "TypeScript",
18980        FakeLspAdapter {
18981            capabilities: lsp::ServerCapabilities {
18982                code_lens_provider: Some(lsp::CodeLensOptions {
18983                    resolve_provider: Some(true),
18984                }),
18985                execute_command_provider: Some(lsp::ExecuteCommandOptions {
18986                    commands: vec!["_the/command".to_string()],
18987                    ..lsp::ExecuteCommandOptions::default()
18988                }),
18989                ..lsp::ServerCapabilities::default()
18990            },
18991            ..FakeLspAdapter::default()
18992        },
18993    );
18994
18995    let (buffer, _handle) = project
18996        .update(cx, |p, cx| {
18997            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18998        })
18999        .await
19000        .unwrap();
19001    cx.executor().run_until_parked();
19002
19003    let fake_server = fake_language_servers.next().await.unwrap();
19004
19005    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19006    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19007    drop(buffer_snapshot);
19008    let actions = cx
19009        .update_window(*workspace, |_, window, cx| {
19010            project.code_actions(&buffer, anchor..anchor, window, cx)
19011        })
19012        .unwrap();
19013
19014    fake_server
19015        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19016            Ok(Some(vec![
19017                lsp::CodeLens {
19018                    range: lsp::Range::default(),
19019                    command: Some(lsp::Command {
19020                        title: "Code lens command".to_owned(),
19021                        command: "_the/command".to_owned(),
19022                        arguments: None,
19023                    }),
19024                    data: None,
19025                },
19026                lsp::CodeLens {
19027                    range: lsp::Range::default(),
19028                    command: Some(lsp::Command {
19029                        title: "Command not in capabilities".to_owned(),
19030                        command: "not in capabilities".to_owned(),
19031                        arguments: None,
19032                    }),
19033                    data: None,
19034                },
19035                lsp::CodeLens {
19036                    range: lsp::Range {
19037                        start: lsp::Position {
19038                            line: 1,
19039                            character: 1,
19040                        },
19041                        end: lsp::Position {
19042                            line: 1,
19043                            character: 1,
19044                        },
19045                    },
19046                    command: Some(lsp::Command {
19047                        title: "Command not in range".to_owned(),
19048                        command: "_the/command".to_owned(),
19049                        arguments: None,
19050                    }),
19051                    data: None,
19052                },
19053            ]))
19054        })
19055        .next()
19056        .await;
19057
19058    let actions = actions.await.unwrap();
19059    assert_eq!(
19060        actions.len(),
19061        1,
19062        "Should have only one valid action for the 0..0 range"
19063    );
19064    let action = actions[0].clone();
19065    let apply = project.update(cx, |project, cx| {
19066        project.apply_code_action(buffer.clone(), action, true, cx)
19067    });
19068
19069    // Resolving the code action does not populate its edits. In absence of
19070    // edits, we must execute the given command.
19071    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19072        |mut lens, _| async move {
19073            let lens_command = lens.command.as_mut().expect("should have a command");
19074            assert_eq!(lens_command.title, "Code lens command");
19075            lens_command.arguments = Some(vec![json!("the-argument")]);
19076            Ok(lens)
19077        },
19078    );
19079
19080    // While executing the command, the language server sends the editor
19081    // a `workspaceEdit` request.
19082    fake_server
19083        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19084            let fake = fake_server.clone();
19085            move |params, _| {
19086                assert_eq!(params.command, "_the/command");
19087                let fake = fake.clone();
19088                async move {
19089                    fake.server
19090                        .request::<lsp::request::ApplyWorkspaceEdit>(
19091                            lsp::ApplyWorkspaceEditParams {
19092                                label: None,
19093                                edit: lsp::WorkspaceEdit {
19094                                    changes: Some(
19095                                        [(
19096                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19097                                            vec![lsp::TextEdit {
19098                                                range: lsp::Range::new(
19099                                                    lsp::Position::new(0, 0),
19100                                                    lsp::Position::new(0, 0),
19101                                                ),
19102                                                new_text: "X".into(),
19103                                            }],
19104                                        )]
19105                                        .into_iter()
19106                                        .collect(),
19107                                    ),
19108                                    ..Default::default()
19109                                },
19110                            },
19111                        )
19112                        .await
19113                        .unwrap();
19114                    Ok(Some(json!(null)))
19115                }
19116            }
19117        })
19118        .next()
19119        .await;
19120
19121    // Applying the code lens command returns a project transaction containing the edits
19122    // sent by the language server in its `workspaceEdit` request.
19123    let transaction = apply.await.unwrap();
19124    assert!(transaction.0.contains_key(&buffer));
19125    buffer.update(cx, |buffer, cx| {
19126        assert_eq!(buffer.text(), "Xa");
19127        buffer.undo(cx);
19128        assert_eq!(buffer.text(), "a");
19129    });
19130}
19131
19132#[gpui::test]
19133async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19134    init_test(cx, |_| {});
19135
19136    let fs = FakeFs::new(cx.executor());
19137    let main_text = r#"fn main() {
19138println!("1");
19139println!("2");
19140println!("3");
19141println!("4");
19142println!("5");
19143}"#;
19144    let lib_text = "mod foo {}";
19145    fs.insert_tree(
19146        path!("/a"),
19147        json!({
19148            "lib.rs": lib_text,
19149            "main.rs": main_text,
19150        }),
19151    )
19152    .await;
19153
19154    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19155    let (workspace, cx) =
19156        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19157    let worktree_id = workspace.update(cx, |workspace, cx| {
19158        workspace.project().update(cx, |project, cx| {
19159            project.worktrees(cx).next().unwrap().read(cx).id()
19160        })
19161    });
19162
19163    let expected_ranges = vec![
19164        Point::new(0, 0)..Point::new(0, 0),
19165        Point::new(1, 0)..Point::new(1, 1),
19166        Point::new(2, 0)..Point::new(2, 2),
19167        Point::new(3, 0)..Point::new(3, 3),
19168    ];
19169
19170    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19171    let editor_1 = workspace
19172        .update_in(cx, |workspace, window, cx| {
19173            workspace.open_path(
19174                (worktree_id, "main.rs"),
19175                Some(pane_1.downgrade()),
19176                true,
19177                window,
19178                cx,
19179            )
19180        })
19181        .unwrap()
19182        .await
19183        .downcast::<Editor>()
19184        .unwrap();
19185    pane_1.update(cx, |pane, cx| {
19186        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19187        open_editor.update(cx, |editor, cx| {
19188            assert_eq!(
19189                editor.display_text(cx),
19190                main_text,
19191                "Original main.rs text on initial open",
19192            );
19193            assert_eq!(
19194                editor
19195                    .selections
19196                    .all::<Point>(cx)
19197                    .into_iter()
19198                    .map(|s| s.range())
19199                    .collect::<Vec<_>>(),
19200                vec![Point::zero()..Point::zero()],
19201                "Default selections on initial open",
19202            );
19203        })
19204    });
19205    editor_1.update_in(cx, |editor, window, cx| {
19206        editor.change_selections(None, window, cx, |s| {
19207            s.select_ranges(expected_ranges.clone());
19208        });
19209    });
19210
19211    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19212        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19213    });
19214    let editor_2 = workspace
19215        .update_in(cx, |workspace, window, cx| {
19216            workspace.open_path(
19217                (worktree_id, "main.rs"),
19218                Some(pane_2.downgrade()),
19219                true,
19220                window,
19221                cx,
19222            )
19223        })
19224        .unwrap()
19225        .await
19226        .downcast::<Editor>()
19227        .unwrap();
19228    pane_2.update(cx, |pane, cx| {
19229        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19230        open_editor.update(cx, |editor, cx| {
19231            assert_eq!(
19232                editor.display_text(cx),
19233                main_text,
19234                "Original main.rs text on initial open in another panel",
19235            );
19236            assert_eq!(
19237                editor
19238                    .selections
19239                    .all::<Point>(cx)
19240                    .into_iter()
19241                    .map(|s| s.range())
19242                    .collect::<Vec<_>>(),
19243                vec![Point::zero()..Point::zero()],
19244                "Default selections on initial open in another panel",
19245            );
19246        })
19247    });
19248
19249    editor_2.update_in(cx, |editor, window, cx| {
19250        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19251    });
19252
19253    let _other_editor_1 = workspace
19254        .update_in(cx, |workspace, window, cx| {
19255            workspace.open_path(
19256                (worktree_id, "lib.rs"),
19257                Some(pane_1.downgrade()),
19258                true,
19259                window,
19260                cx,
19261            )
19262        })
19263        .unwrap()
19264        .await
19265        .downcast::<Editor>()
19266        .unwrap();
19267    pane_1
19268        .update_in(cx, |pane, window, cx| {
19269            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19270                .unwrap()
19271        })
19272        .await
19273        .unwrap();
19274    drop(editor_1);
19275    pane_1.update(cx, |pane, cx| {
19276        pane.active_item()
19277            .unwrap()
19278            .downcast::<Editor>()
19279            .unwrap()
19280            .update(cx, |editor, cx| {
19281                assert_eq!(
19282                    editor.display_text(cx),
19283                    lib_text,
19284                    "Other file should be open and active",
19285                );
19286            });
19287        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19288    });
19289
19290    let _other_editor_2 = workspace
19291        .update_in(cx, |workspace, window, cx| {
19292            workspace.open_path(
19293                (worktree_id, "lib.rs"),
19294                Some(pane_2.downgrade()),
19295                true,
19296                window,
19297                cx,
19298            )
19299        })
19300        .unwrap()
19301        .await
19302        .downcast::<Editor>()
19303        .unwrap();
19304    pane_2
19305        .update_in(cx, |pane, window, cx| {
19306            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19307                .unwrap()
19308        })
19309        .await
19310        .unwrap();
19311    drop(editor_2);
19312    pane_2.update(cx, |pane, cx| {
19313        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19314        open_editor.update(cx, |editor, cx| {
19315            assert_eq!(
19316                editor.display_text(cx),
19317                lib_text,
19318                "Other file should be open and active in another panel too",
19319            );
19320        });
19321        assert_eq!(
19322            pane.items().count(),
19323            1,
19324            "No other editors should be open in another pane",
19325        );
19326    });
19327
19328    let _editor_1_reopened = workspace
19329        .update_in(cx, |workspace, window, cx| {
19330            workspace.open_path(
19331                (worktree_id, "main.rs"),
19332                Some(pane_1.downgrade()),
19333                true,
19334                window,
19335                cx,
19336            )
19337        })
19338        .unwrap()
19339        .await
19340        .downcast::<Editor>()
19341        .unwrap();
19342    let _editor_2_reopened = workspace
19343        .update_in(cx, |workspace, window, cx| {
19344            workspace.open_path(
19345                (worktree_id, "main.rs"),
19346                Some(pane_2.downgrade()),
19347                true,
19348                window,
19349                cx,
19350            )
19351        })
19352        .unwrap()
19353        .await
19354        .downcast::<Editor>()
19355        .unwrap();
19356    pane_1.update(cx, |pane, cx| {
19357        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19358        open_editor.update(cx, |editor, cx| {
19359            assert_eq!(
19360                editor.display_text(cx),
19361                main_text,
19362                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19363            );
19364            assert_eq!(
19365                editor
19366                    .selections
19367                    .all::<Point>(cx)
19368                    .into_iter()
19369                    .map(|s| s.range())
19370                    .collect::<Vec<_>>(),
19371                expected_ranges,
19372                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19373            );
19374        })
19375    });
19376    pane_2.update(cx, |pane, cx| {
19377        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19378        open_editor.update(cx, |editor, cx| {
19379            assert_eq!(
19380                editor.display_text(cx),
19381                r#"fn main() {
19382⋯rintln!("1");
19383⋯intln!("2");
19384⋯ntln!("3");
19385println!("4");
19386println!("5");
19387}"#,
19388                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19389            );
19390            assert_eq!(
19391                editor
19392                    .selections
19393                    .all::<Point>(cx)
19394                    .into_iter()
19395                    .map(|s| s.range())
19396                    .collect::<Vec<_>>(),
19397                vec![Point::zero()..Point::zero()],
19398                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19399            );
19400        })
19401    });
19402}
19403
19404#[gpui::test]
19405async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19406    init_test(cx, |_| {});
19407
19408    let fs = FakeFs::new(cx.executor());
19409    let main_text = r#"fn main() {
19410println!("1");
19411println!("2");
19412println!("3");
19413println!("4");
19414println!("5");
19415}"#;
19416    let lib_text = "mod foo {}";
19417    fs.insert_tree(
19418        path!("/a"),
19419        json!({
19420            "lib.rs": lib_text,
19421            "main.rs": main_text,
19422        }),
19423    )
19424    .await;
19425
19426    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19427    let (workspace, cx) =
19428        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19429    let worktree_id = workspace.update(cx, |workspace, cx| {
19430        workspace.project().update(cx, |project, cx| {
19431            project.worktrees(cx).next().unwrap().read(cx).id()
19432        })
19433    });
19434
19435    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19436    let editor = workspace
19437        .update_in(cx, |workspace, window, cx| {
19438            workspace.open_path(
19439                (worktree_id, "main.rs"),
19440                Some(pane.downgrade()),
19441                true,
19442                window,
19443                cx,
19444            )
19445        })
19446        .unwrap()
19447        .await
19448        .downcast::<Editor>()
19449        .unwrap();
19450    pane.update(cx, |pane, cx| {
19451        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19452        open_editor.update(cx, |editor, cx| {
19453            assert_eq!(
19454                editor.display_text(cx),
19455                main_text,
19456                "Original main.rs text on initial open",
19457            );
19458        })
19459    });
19460    editor.update_in(cx, |editor, window, cx| {
19461        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19462    });
19463
19464    cx.update_global(|store: &mut SettingsStore, cx| {
19465        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19466            s.restore_on_file_reopen = Some(false);
19467        });
19468    });
19469    editor.update_in(cx, |editor, window, cx| {
19470        editor.fold_ranges(
19471            vec![
19472                Point::new(1, 0)..Point::new(1, 1),
19473                Point::new(2, 0)..Point::new(2, 2),
19474                Point::new(3, 0)..Point::new(3, 3),
19475            ],
19476            false,
19477            window,
19478            cx,
19479        );
19480    });
19481    pane.update_in(cx, |pane, window, cx| {
19482        pane.close_all_items(&CloseAllItems::default(), window, cx)
19483            .unwrap()
19484    })
19485    .await
19486    .unwrap();
19487    pane.update(cx, |pane, _| {
19488        assert!(pane.active_item().is_none());
19489    });
19490    cx.update_global(|store: &mut SettingsStore, cx| {
19491        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19492            s.restore_on_file_reopen = Some(true);
19493        });
19494    });
19495
19496    let _editor_reopened = workspace
19497        .update_in(cx, |workspace, window, cx| {
19498            workspace.open_path(
19499                (worktree_id, "main.rs"),
19500                Some(pane.downgrade()),
19501                true,
19502                window,
19503                cx,
19504            )
19505        })
19506        .unwrap()
19507        .await
19508        .downcast::<Editor>()
19509        .unwrap();
19510    pane.update(cx, |pane, cx| {
19511        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19512        open_editor.update(cx, |editor, cx| {
19513            assert_eq!(
19514                editor.display_text(cx),
19515                main_text,
19516                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19517            );
19518        })
19519    });
19520}
19521
19522fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19523    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19524    point..point
19525}
19526
19527fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19528    let (text, ranges) = marked_text_ranges(marked_text, true);
19529    assert_eq!(editor.text(cx), text);
19530    assert_eq!(
19531        editor.selections.ranges(cx),
19532        ranges,
19533        "Assert selections are {}",
19534        marked_text
19535    );
19536}
19537
19538pub fn handle_signature_help_request(
19539    cx: &mut EditorLspTestContext,
19540    mocked_response: lsp::SignatureHelp,
19541) -> impl Future<Output = ()> + use<> {
19542    let mut request =
19543        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19544            let mocked_response = mocked_response.clone();
19545            async move { Ok(Some(mocked_response)) }
19546        });
19547
19548    async move {
19549        request.next().await;
19550    }
19551}
19552
19553/// Handle completion request passing a marked string specifying where the completion
19554/// should be triggered from using '|' character, what range should be replaced, and what completions
19555/// should be returned using '<' and '>' to delimit the range.
19556///
19557/// Also see `handle_completion_request_with_insert_and_replace`.
19558#[track_caller]
19559pub fn handle_completion_request(
19560    cx: &mut EditorLspTestContext,
19561    marked_string: &str,
19562    completions: Vec<&'static str>,
19563    counter: Arc<AtomicUsize>,
19564) -> impl Future<Output = ()> {
19565    let complete_from_marker: TextRangeMarker = '|'.into();
19566    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19567    let (_, mut marked_ranges) = marked_text_ranges_by(
19568        marked_string,
19569        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19570    );
19571
19572    let complete_from_position =
19573        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19574    let replace_range =
19575        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19576
19577    let mut request =
19578        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19579            let completions = completions.clone();
19580            counter.fetch_add(1, atomic::Ordering::Release);
19581            async move {
19582                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19583                assert_eq!(
19584                    params.text_document_position.position,
19585                    complete_from_position
19586                );
19587                Ok(Some(lsp::CompletionResponse::Array(
19588                    completions
19589                        .iter()
19590                        .map(|completion_text| lsp::CompletionItem {
19591                            label: completion_text.to_string(),
19592                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19593                                range: replace_range,
19594                                new_text: completion_text.to_string(),
19595                            })),
19596                            ..Default::default()
19597                        })
19598                        .collect(),
19599                )))
19600            }
19601        });
19602
19603    async move {
19604        request.next().await;
19605    }
19606}
19607
19608/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19609/// given instead, which also contains an `insert` range.
19610///
19611/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19612/// that is, `replace_range.start..cursor_pos`.
19613pub fn handle_completion_request_with_insert_and_replace(
19614    cx: &mut EditorLspTestContext,
19615    marked_string: &str,
19616    completions: Vec<&'static str>,
19617    counter: Arc<AtomicUsize>,
19618) -> impl Future<Output = ()> {
19619    let complete_from_marker: TextRangeMarker = '|'.into();
19620    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19621    let (_, mut marked_ranges) = marked_text_ranges_by(
19622        marked_string,
19623        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19624    );
19625
19626    let complete_from_position =
19627        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19628    let replace_range =
19629        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19630
19631    let mut request =
19632        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19633            let completions = completions.clone();
19634            counter.fetch_add(1, atomic::Ordering::Release);
19635            async move {
19636                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19637                assert_eq!(
19638                    params.text_document_position.position, complete_from_position,
19639                    "marker `|` position doesn't match",
19640                );
19641                Ok(Some(lsp::CompletionResponse::Array(
19642                    completions
19643                        .iter()
19644                        .map(|completion_text| lsp::CompletionItem {
19645                            label: completion_text.to_string(),
19646                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19647                                lsp::InsertReplaceEdit {
19648                                    insert: lsp::Range {
19649                                        start: replace_range.start,
19650                                        end: complete_from_position,
19651                                    },
19652                                    replace: replace_range,
19653                                    new_text: completion_text.to_string(),
19654                                },
19655                            )),
19656                            ..Default::default()
19657                        })
19658                        .collect(),
19659                )))
19660            }
19661        });
19662
19663    async move {
19664        request.next().await;
19665    }
19666}
19667
19668fn handle_resolve_completion_request(
19669    cx: &mut EditorLspTestContext,
19670    edits: Option<Vec<(&'static str, &'static str)>>,
19671) -> impl Future<Output = ()> {
19672    let edits = edits.map(|edits| {
19673        edits
19674            .iter()
19675            .map(|(marked_string, new_text)| {
19676                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19677                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19678                lsp::TextEdit::new(replace_range, new_text.to_string())
19679            })
19680            .collect::<Vec<_>>()
19681    });
19682
19683    let mut request =
19684        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19685            let edits = edits.clone();
19686            async move {
19687                Ok(lsp::CompletionItem {
19688                    additional_text_edits: edits,
19689                    ..Default::default()
19690                })
19691            }
19692        });
19693
19694    async move {
19695        request.next().await;
19696    }
19697}
19698
19699pub(crate) fn update_test_language_settings(
19700    cx: &mut TestAppContext,
19701    f: impl Fn(&mut AllLanguageSettingsContent),
19702) {
19703    cx.update(|cx| {
19704        SettingsStore::update_global(cx, |store, cx| {
19705            store.update_user_settings::<AllLanguageSettings>(cx, f);
19706        });
19707    });
19708}
19709
19710pub(crate) fn update_test_project_settings(
19711    cx: &mut TestAppContext,
19712    f: impl Fn(&mut ProjectSettings),
19713) {
19714    cx.update(|cx| {
19715        SettingsStore::update_global(cx, |store, cx| {
19716            store.update_user_settings::<ProjectSettings>(cx, f);
19717        });
19718    });
19719}
19720
19721pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19722    cx.update(|cx| {
19723        assets::Assets.load_test_fonts(cx);
19724        let store = SettingsStore::test(cx);
19725        cx.set_global(store);
19726        theme::init(theme::LoadThemes::JustBase, cx);
19727        release_channel::init(SemanticVersion::default(), cx);
19728        client::init_settings(cx);
19729        language::init(cx);
19730        Project::init_settings(cx);
19731        workspace::init_settings(cx);
19732        crate::init(cx);
19733    });
19734
19735    update_test_language_settings(cx, f);
19736}
19737
19738#[track_caller]
19739fn assert_hunk_revert(
19740    not_reverted_text_with_selections: &str,
19741    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19742    expected_reverted_text_with_selections: &str,
19743    base_text: &str,
19744    cx: &mut EditorLspTestContext,
19745) {
19746    cx.set_state(not_reverted_text_with_selections);
19747    cx.set_head_text(base_text);
19748    cx.executor().run_until_parked();
19749
19750    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19751        let snapshot = editor.snapshot(window, cx);
19752        let reverted_hunk_statuses = snapshot
19753            .buffer_snapshot
19754            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19755            .map(|hunk| hunk.status().kind)
19756            .collect::<Vec<_>>();
19757
19758        editor.git_restore(&Default::default(), window, cx);
19759        reverted_hunk_statuses
19760    });
19761    cx.executor().run_until_parked();
19762    cx.assert_editor_state(expected_reverted_text_with_selections);
19763    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19764}