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// TODO: Re-enable this test
 1320#[cfg(target_os = "macos")]
 1321#[gpui::test]
 1322fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1323    init_test(cx, |_| {});
 1324
 1325    let editor = cx.add_window(|window, cx| {
 1326        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1327        build_editor(buffer.clone(), window, cx)
 1328    });
 1329
 1330    assert_eq!('🟥'.len_utf8(), 4);
 1331    assert_eq!('α'.len_utf8(), 2);
 1332
 1333    _ = editor.update(cx, |editor, window, cx| {
 1334        editor.fold_creases(
 1335            vec![
 1336                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1337                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1338                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1339            ],
 1340            true,
 1341            window,
 1342            cx,
 1343        );
 1344        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1345
 1346        editor.move_right(&MoveRight, window, cx);
 1347        assert_eq!(
 1348            editor.selections.display_ranges(cx),
 1349            &[empty_range(0, "🟥".len())]
 1350        );
 1351        editor.move_right(&MoveRight, window, cx);
 1352        assert_eq!(
 1353            editor.selections.display_ranges(cx),
 1354            &[empty_range(0, "🟥🟧".len())]
 1355        );
 1356        editor.move_right(&MoveRight, window, cx);
 1357        assert_eq!(
 1358            editor.selections.display_ranges(cx),
 1359            &[empty_range(0, "🟥🟧⋯".len())]
 1360        );
 1361
 1362        editor.move_down(&MoveDown, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(1, "ab⋯e".len())]
 1366        );
 1367        editor.move_left(&MoveLeft, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(1, "ab⋯".len())]
 1371        );
 1372        editor.move_left(&MoveLeft, window, cx);
 1373        assert_eq!(
 1374            editor.selections.display_ranges(cx),
 1375            &[empty_range(1, "ab".len())]
 1376        );
 1377        editor.move_left(&MoveLeft, window, cx);
 1378        assert_eq!(
 1379            editor.selections.display_ranges(cx),
 1380            &[empty_range(1, "a".len())]
 1381        );
 1382
 1383        editor.move_down(&MoveDown, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(2, "α".len())]
 1387        );
 1388        editor.move_right(&MoveRight, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(2, "αβ".len())]
 1392        );
 1393        editor.move_right(&MoveRight, window, cx);
 1394        assert_eq!(
 1395            editor.selections.display_ranges(cx),
 1396            &[empty_range(2, "αβ⋯".len())]
 1397        );
 1398        editor.move_right(&MoveRight, window, cx);
 1399        assert_eq!(
 1400            editor.selections.display_ranges(cx),
 1401            &[empty_range(2, "αβ⋯ε".len())]
 1402        );
 1403
 1404        editor.move_up(&MoveUp, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(1, "ab⋯e".len())]
 1408        );
 1409        editor.move_down(&MoveDown, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414        editor.move_up(&MoveUp, window, cx);
 1415        assert_eq!(
 1416            editor.selections.display_ranges(cx),
 1417            &[empty_range(1, "ab⋯e".len())]
 1418        );
 1419
 1420        editor.move_up(&MoveUp, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(0, "🟥🟧".len())]
 1424        );
 1425        editor.move_left(&MoveLeft, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(0, "🟥".len())]
 1429        );
 1430        editor.move_left(&MoveLeft, window, cx);
 1431        assert_eq!(
 1432            editor.selections.display_ranges(cx),
 1433            &[empty_range(0, "".len())]
 1434        );
 1435    });
 1436}
 1437
 1438#[gpui::test]
 1439fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1440    init_test(cx, |_| {});
 1441
 1442    let editor = cx.add_window(|window, cx| {
 1443        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1444        build_editor(buffer.clone(), window, cx)
 1445    });
 1446    _ = editor.update(cx, |editor, window, cx| {
 1447        editor.change_selections(None, window, cx, |s| {
 1448            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1449        });
 1450
 1451        // moving above start of document should move selection to start of document,
 1452        // but the next move down should still be at the original goal_x
 1453        editor.move_up(&MoveUp, window, cx);
 1454        assert_eq!(
 1455            editor.selections.display_ranges(cx),
 1456            &[empty_range(0, "".len())]
 1457        );
 1458
 1459        editor.move_down(&MoveDown, window, cx);
 1460        assert_eq!(
 1461            editor.selections.display_ranges(cx),
 1462            &[empty_range(1, "abcd".len())]
 1463        );
 1464
 1465        editor.move_down(&MoveDown, window, cx);
 1466        assert_eq!(
 1467            editor.selections.display_ranges(cx),
 1468            &[empty_range(2, "αβγ".len())]
 1469        );
 1470
 1471        editor.move_down(&MoveDown, window, cx);
 1472        assert_eq!(
 1473            editor.selections.display_ranges(cx),
 1474            &[empty_range(3, "abcd".len())]
 1475        );
 1476
 1477        editor.move_down(&MoveDown, window, cx);
 1478        assert_eq!(
 1479            editor.selections.display_ranges(cx),
 1480            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1481        );
 1482
 1483        // moving past end of document should not change goal_x
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(5, "".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(5, "".len())]
 1494        );
 1495
 1496        editor.move_up(&MoveUp, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1500        );
 1501
 1502        editor.move_up(&MoveUp, window, cx);
 1503        assert_eq!(
 1504            editor.selections.display_ranges(cx),
 1505            &[empty_range(3, "abcd".len())]
 1506        );
 1507
 1508        editor.move_up(&MoveUp, window, cx);
 1509        assert_eq!(
 1510            editor.selections.display_ranges(cx),
 1511            &[empty_range(2, "αβγ".len())]
 1512        );
 1513    });
 1514}
 1515
 1516#[gpui::test]
 1517fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1518    init_test(cx, |_| {});
 1519    let move_to_beg = MoveToBeginningOfLine {
 1520        stop_at_soft_wraps: true,
 1521        stop_at_indent: true,
 1522    };
 1523
 1524    let delete_to_beg = DeleteToBeginningOfLine {
 1525        stop_at_indent: false,
 1526    };
 1527
 1528    let move_to_end = MoveToEndOfLine {
 1529        stop_at_soft_wraps: true,
 1530    };
 1531
 1532    let editor = cx.add_window(|window, cx| {
 1533        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1534        build_editor(buffer, window, cx)
 1535    });
 1536    _ = editor.update(cx, |editor, window, cx| {
 1537        editor.change_selections(None, window, cx, |s| {
 1538            s.select_display_ranges([
 1539                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1540                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1541            ]);
 1542        });
 1543    });
 1544
 1545    _ = editor.update(cx, |editor, window, cx| {
 1546        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1547        assert_eq!(
 1548            editor.selections.display_ranges(cx),
 1549            &[
 1550                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1551                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1552            ]
 1553        );
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_end_of_line(&move_to_end, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1584                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1585            ]
 1586        );
 1587    });
 1588
 1589    // Moving to the end of line again is a no-op.
 1590    _ = editor.update(cx, |editor, window, cx| {
 1591        editor.move_to_end_of_line(&move_to_end, window, cx);
 1592        assert_eq!(
 1593            editor.selections.display_ranges(cx),
 1594            &[
 1595                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1596                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1597            ]
 1598        );
 1599    });
 1600
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_left(&MoveLeft, window, cx);
 1603        editor.select_to_beginning_of_line(
 1604            &SelectToBeginningOfLine {
 1605                stop_at_soft_wraps: true,
 1606                stop_at_indent: true,
 1607            },
 1608            window,
 1609            cx,
 1610        );
 1611        assert_eq!(
 1612            editor.selections.display_ranges(cx),
 1613            &[
 1614                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1615                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1616            ]
 1617        );
 1618    });
 1619
 1620    _ = editor.update(cx, |editor, window, cx| {
 1621        editor.select_to_beginning_of_line(
 1622            &SelectToBeginningOfLine {
 1623                stop_at_soft_wraps: true,
 1624                stop_at_indent: true,
 1625            },
 1626            window,
 1627            cx,
 1628        );
 1629        assert_eq!(
 1630            editor.selections.display_ranges(cx),
 1631            &[
 1632                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1633                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1634            ]
 1635        );
 1636    });
 1637
 1638    _ = editor.update(cx, |editor, window, cx| {
 1639        editor.select_to_beginning_of_line(
 1640            &SelectToBeginningOfLine {
 1641                stop_at_soft_wraps: true,
 1642                stop_at_indent: true,
 1643            },
 1644            window,
 1645            cx,
 1646        );
 1647        assert_eq!(
 1648            editor.selections.display_ranges(cx),
 1649            &[
 1650                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1651                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1652            ]
 1653        );
 1654    });
 1655
 1656    _ = editor.update(cx, |editor, window, cx| {
 1657        editor.select_to_end_of_line(
 1658            &SelectToEndOfLine {
 1659                stop_at_soft_wraps: true,
 1660            },
 1661            window,
 1662            cx,
 1663        );
 1664        assert_eq!(
 1665            editor.selections.display_ranges(cx),
 1666            &[
 1667                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1668                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1669            ]
 1670        );
 1671    });
 1672
 1673    _ = editor.update(cx, |editor, window, cx| {
 1674        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1675        assert_eq!(editor.display_text(cx), "ab\n  de");
 1676        assert_eq!(
 1677            editor.selections.display_ranges(cx),
 1678            &[
 1679                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1680                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1681            ]
 1682        );
 1683    });
 1684
 1685    _ = editor.update(cx, |editor, window, cx| {
 1686        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1687        assert_eq!(editor.display_text(cx), "\n");
 1688        assert_eq!(
 1689            editor.selections.display_ranges(cx),
 1690            &[
 1691                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1692                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1693            ]
 1694        );
 1695    });
 1696}
 1697
 1698#[gpui::test]
 1699fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1700    init_test(cx, |_| {});
 1701    let move_to_beg = MoveToBeginningOfLine {
 1702        stop_at_soft_wraps: false,
 1703        stop_at_indent: false,
 1704    };
 1705
 1706    let move_to_end = MoveToEndOfLine {
 1707        stop_at_soft_wraps: false,
 1708    };
 1709
 1710    let editor = cx.add_window(|window, cx| {
 1711        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1712        build_editor(buffer, window, cx)
 1713    });
 1714
 1715    _ = editor.update(cx, |editor, window, cx| {
 1716        editor.set_wrap_width(Some(140.0.into()), cx);
 1717
 1718        // We expect the following lines after wrapping
 1719        // ```
 1720        // thequickbrownfox
 1721        // jumpedoverthelazydo
 1722        // gs
 1723        // ```
 1724        // The final `gs` was soft-wrapped onto a new line.
 1725        assert_eq!(
 1726            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1727            editor.display_text(cx),
 1728        );
 1729
 1730        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1731        // Start the cursor at the `k` on the first line
 1732        editor.change_selections(None, window, cx, |s| {
 1733            s.select_display_ranges([
 1734                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1735            ]);
 1736        });
 1737
 1738        // Moving to the beginning of the line should put us at the beginning of the line.
 1739        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1740        assert_eq!(
 1741            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1742            editor.selections.display_ranges(cx)
 1743        );
 1744
 1745        // Moving to the end of the line should put us at the end of the line.
 1746        editor.move_to_end_of_line(&move_to_end, window, cx);
 1747        assert_eq!(
 1748            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1749            editor.selections.display_ranges(cx)
 1750        );
 1751
 1752        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1753        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1754        editor.change_selections(None, window, cx, |s| {
 1755            s.select_display_ranges([
 1756                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1757            ]);
 1758        });
 1759
 1760        // Moving to the beginning of the line should put us at the start of the second line of
 1761        // display text, i.e., the `j`.
 1762        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1763        assert_eq!(
 1764            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1765            editor.selections.display_ranges(cx)
 1766        );
 1767
 1768        // Moving to the beginning of the line again should be a no-op.
 1769        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1770        assert_eq!(
 1771            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1772            editor.selections.display_ranges(cx)
 1773        );
 1774
 1775        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1776        // next display line.
 1777        editor.move_to_end_of_line(&move_to_end, window, cx);
 1778        assert_eq!(
 1779            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1780            editor.selections.display_ranges(cx)
 1781        );
 1782
 1783        // Moving to the end of the line again should be a no-op.
 1784        editor.move_to_end_of_line(&move_to_end, window, cx);
 1785        assert_eq!(
 1786            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1787            editor.selections.display_ranges(cx)
 1788        );
 1789    });
 1790}
 1791
 1792#[gpui::test]
 1793fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1794    init_test(cx, |_| {});
 1795
 1796    let move_to_beg = MoveToBeginningOfLine {
 1797        stop_at_soft_wraps: true,
 1798        stop_at_indent: true,
 1799    };
 1800
 1801    let select_to_beg = SelectToBeginningOfLine {
 1802        stop_at_soft_wraps: true,
 1803        stop_at_indent: true,
 1804    };
 1805
 1806    let delete_to_beg = DeleteToBeginningOfLine {
 1807        stop_at_indent: true,
 1808    };
 1809
 1810    let move_to_end = MoveToEndOfLine {
 1811        stop_at_soft_wraps: false,
 1812    };
 1813
 1814    let editor = cx.add_window(|window, cx| {
 1815        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1816        build_editor(buffer, window, cx)
 1817    });
 1818
 1819    _ = editor.update(cx, |editor, window, cx| {
 1820        editor.change_selections(None, window, cx, |s| {
 1821            s.select_display_ranges([
 1822                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1823                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1824            ]);
 1825        });
 1826
 1827        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1828        // and the second cursor at the first non-whitespace character in the line.
 1829        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1830        assert_eq!(
 1831            editor.selections.display_ranges(cx),
 1832            &[
 1833                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1834                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1835            ]
 1836        );
 1837
 1838        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1839        // and should move the second cursor to the beginning of the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1850        // and should move the second cursor back to the first non-whitespace character in the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1857            ]
 1858        );
 1859
 1860        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1861        // and to the first non-whitespace character in the line for the second cursor.
 1862        editor.move_to_end_of_line(&move_to_end, window, cx);
 1863        editor.move_left(&MoveLeft, window, cx);
 1864        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1874        // and should select to the beginning of the line for the second cursor.
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883
 1884        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1885        // and should delete to the first non-whitespace character in the line for the second cursor.
 1886        editor.move_to_end_of_line(&move_to_end, window, cx);
 1887        editor.move_left(&MoveLeft, window, cx);
 1888        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1889        assert_eq!(editor.text(cx), "c\n  f");
 1890    });
 1891}
 1892
 1893#[gpui::test]
 1894fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1895    init_test(cx, |_| {});
 1896
 1897    let editor = cx.add_window(|window, cx| {
 1898        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1899        build_editor(buffer, window, cx)
 1900    });
 1901    _ = editor.update(cx, |editor, window, cx| {
 1902        editor.change_selections(None, window, cx, |s| {
 1903            s.select_display_ranges([
 1904                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1905                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1906            ])
 1907        });
 1908
 1909        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1910        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1911
 1912        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1913        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1914
 1915        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1916        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1917
 1918        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1919        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1920
 1921        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1922        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1923
 1924        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1925        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1926
 1927        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1928        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1929
 1930        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1931        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1932
 1933        editor.move_right(&MoveRight, window, cx);
 1934        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1935        assert_selection_ranges(
 1936            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1937            editor,
 1938            cx,
 1939        );
 1940
 1941        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1942        assert_selection_ranges(
 1943            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1944            editor,
 1945            cx,
 1946        );
 1947
 1948        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1949        assert_selection_ranges(
 1950            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1951            editor,
 1952            cx,
 1953        );
 1954    });
 1955}
 1956
 1957#[gpui::test]
 1958fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1959    init_test(cx, |_| {});
 1960
 1961    let editor = cx.add_window(|window, cx| {
 1962        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1963        build_editor(buffer, window, cx)
 1964    });
 1965
 1966    _ = editor.update(cx, |editor, window, cx| {
 1967        editor.set_wrap_width(Some(140.0.into()), cx);
 1968        assert_eq!(
 1969            editor.display_text(cx),
 1970            "use one::{\n    two::three::\n    four::five\n};"
 1971        );
 1972
 1973        editor.change_selections(None, window, cx, |s| {
 1974            s.select_display_ranges([
 1975                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1976            ]);
 1977        });
 1978
 1979        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1980        assert_eq!(
 1981            editor.selections.display_ranges(cx),
 1982            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1983        );
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_eq!(
 1987            editor.selections.display_ranges(cx),
 1988            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1989        );
 1990
 1991        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1992        assert_eq!(
 1993            editor.selections.display_ranges(cx),
 1994            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1995        );
 1996
 1997        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1998        assert_eq!(
 1999            editor.selections.display_ranges(cx),
 2000            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2001        );
 2002
 2003        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2004        assert_eq!(
 2005            editor.selections.display_ranges(cx),
 2006            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2007        );
 2008
 2009        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2010        assert_eq!(
 2011            editor.selections.display_ranges(cx),
 2012            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2013        );
 2014    });
 2015}
 2016
 2017#[gpui::test]
 2018async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2019    init_test(cx, |_| {});
 2020    let mut cx = EditorTestContext::new(cx).await;
 2021
 2022    let line_height = cx.editor(|editor, window, _| {
 2023        editor
 2024            .style()
 2025            .unwrap()
 2026            .text
 2027            .line_height_in_pixels(window.rem_size())
 2028    });
 2029    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2030
 2031    cx.set_state(
 2032        &r#"ˇone
 2033        two
 2034
 2035        three
 2036        fourˇ
 2037        five
 2038
 2039        six"#
 2040            .unindent(),
 2041    );
 2042
 2043    cx.update_editor(|editor, window, cx| {
 2044        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2045    });
 2046    cx.assert_editor_state(
 2047        &r#"one
 2048        two
 2049        ˇ
 2050        three
 2051        four
 2052        five
 2053        ˇ
 2054        six"#
 2055            .unindent(),
 2056    );
 2057
 2058    cx.update_editor(|editor, window, cx| {
 2059        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2060    });
 2061    cx.assert_editor_state(
 2062        &r#"one
 2063        two
 2064
 2065        three
 2066        four
 2067        five
 2068        ˇ
 2069        sixˇ"#
 2070            .unindent(),
 2071    );
 2072
 2073    cx.update_editor(|editor, window, cx| {
 2074        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2075    });
 2076    cx.assert_editor_state(
 2077        &r#"one
 2078        two
 2079
 2080        three
 2081        four
 2082        five
 2083
 2084        sixˇ"#
 2085            .unindent(),
 2086    );
 2087
 2088    cx.update_editor(|editor, window, cx| {
 2089        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2090    });
 2091    cx.assert_editor_state(
 2092        &r#"one
 2093        two
 2094
 2095        three
 2096        four
 2097        five
 2098        ˇ
 2099        six"#
 2100            .unindent(),
 2101    );
 2102
 2103    cx.update_editor(|editor, window, cx| {
 2104        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2105    });
 2106    cx.assert_editor_state(
 2107        &r#"one
 2108        two
 2109        ˇ
 2110        three
 2111        four
 2112        five
 2113
 2114        six"#
 2115            .unindent(),
 2116    );
 2117
 2118    cx.update_editor(|editor, window, cx| {
 2119        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2120    });
 2121    cx.assert_editor_state(
 2122        &r#"ˇone
 2123        two
 2124
 2125        three
 2126        four
 2127        five
 2128
 2129        six"#
 2130            .unindent(),
 2131    );
 2132}
 2133
 2134#[gpui::test]
 2135async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2136    init_test(cx, |_| {});
 2137    let mut cx = EditorTestContext::new(cx).await;
 2138    let line_height = cx.editor(|editor, window, _| {
 2139        editor
 2140            .style()
 2141            .unwrap()
 2142            .text
 2143            .line_height_in_pixels(window.rem_size())
 2144    });
 2145    let window = cx.window;
 2146    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2147
 2148    cx.set_state(
 2149        r#"ˇone
 2150        two
 2151        three
 2152        four
 2153        five
 2154        six
 2155        seven
 2156        eight
 2157        nine
 2158        ten
 2159        "#,
 2160    );
 2161
 2162    cx.update_editor(|editor, window, cx| {
 2163        assert_eq!(
 2164            editor.snapshot(window, cx).scroll_position(),
 2165            gpui::Point::new(0., 0.)
 2166        );
 2167        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2168        assert_eq!(
 2169            editor.snapshot(window, cx).scroll_position(),
 2170            gpui::Point::new(0., 3.)
 2171        );
 2172        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 6.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182
 2183        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2184        assert_eq!(
 2185            editor.snapshot(window, cx).scroll_position(),
 2186            gpui::Point::new(0., 1.)
 2187        );
 2188        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2189        assert_eq!(
 2190            editor.snapshot(window, cx).scroll_position(),
 2191            gpui::Point::new(0., 3.)
 2192        );
 2193    });
 2194}
 2195
 2196#[gpui::test]
 2197async fn test_autoscroll(cx: &mut TestAppContext) {
 2198    init_test(cx, |_| {});
 2199    let mut cx = EditorTestContext::new(cx).await;
 2200
 2201    let line_height = cx.update_editor(|editor, window, cx| {
 2202        editor.set_vertical_scroll_margin(2, cx);
 2203        editor
 2204            .style()
 2205            .unwrap()
 2206            .text
 2207            .line_height_in_pixels(window.rem_size())
 2208    });
 2209    let window = cx.window;
 2210    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2211
 2212    cx.set_state(
 2213        r#"ˇone
 2214            two
 2215            three
 2216            four
 2217            five
 2218            six
 2219            seven
 2220            eight
 2221            nine
 2222            ten
 2223        "#,
 2224    );
 2225    cx.update_editor(|editor, window, cx| {
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 0.0)
 2229        );
 2230    });
 2231
 2232    // Add a cursor below the visible area. Since both cursors cannot fit
 2233    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2234    // allows the vertical scroll margin below that cursor.
 2235    cx.update_editor(|editor, window, cx| {
 2236        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2237            selections.select_ranges([
 2238                Point::new(0, 0)..Point::new(0, 0),
 2239                Point::new(6, 0)..Point::new(6, 0),
 2240            ]);
 2241        })
 2242    });
 2243    cx.update_editor(|editor, window, cx| {
 2244        assert_eq!(
 2245            editor.snapshot(window, cx).scroll_position(),
 2246            gpui::Point::new(0., 3.0)
 2247        );
 2248    });
 2249
 2250    // Move down. The editor cursor scrolls down to track the newest cursor.
 2251    cx.update_editor(|editor, window, cx| {
 2252        editor.move_down(&Default::default(), window, cx);
 2253    });
 2254    cx.update_editor(|editor, window, cx| {
 2255        assert_eq!(
 2256            editor.snapshot(window, cx).scroll_position(),
 2257            gpui::Point::new(0., 4.0)
 2258        );
 2259    });
 2260
 2261    // Add a cursor above the visible area. Since both cursors fit on screen,
 2262    // the editor scrolls to show both.
 2263    cx.update_editor(|editor, window, cx| {
 2264        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2265            selections.select_ranges([
 2266                Point::new(1, 0)..Point::new(1, 0),
 2267                Point::new(6, 0)..Point::new(6, 0),
 2268            ]);
 2269        })
 2270    });
 2271    cx.update_editor(|editor, window, cx| {
 2272        assert_eq!(
 2273            editor.snapshot(window, cx).scroll_position(),
 2274            gpui::Point::new(0., 1.0)
 2275        );
 2276    });
 2277}
 2278
 2279#[gpui::test]
 2280async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2281    init_test(cx, |_| {});
 2282    let mut cx = EditorTestContext::new(cx).await;
 2283
 2284    let line_height = cx.editor(|editor, window, _cx| {
 2285        editor
 2286            .style()
 2287            .unwrap()
 2288            .text
 2289            .line_height_in_pixels(window.rem_size())
 2290    });
 2291    let window = cx.window;
 2292    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2293    cx.set_state(
 2294        &r#"
 2295        ˇone
 2296        two
 2297        threeˇ
 2298        four
 2299        five
 2300        six
 2301        seven
 2302        eight
 2303        nine
 2304        ten
 2305        "#
 2306        .unindent(),
 2307    );
 2308
 2309    cx.update_editor(|editor, window, cx| {
 2310        editor.move_page_down(&MovePageDown::default(), window, cx)
 2311    });
 2312    cx.assert_editor_state(
 2313        &r#"
 2314        one
 2315        two
 2316        three
 2317        ˇfour
 2318        five
 2319        sixˇ
 2320        seven
 2321        eight
 2322        nine
 2323        ten
 2324        "#
 2325        .unindent(),
 2326    );
 2327
 2328    cx.update_editor(|editor, window, cx| {
 2329        editor.move_page_down(&MovePageDown::default(), window, cx)
 2330    });
 2331    cx.assert_editor_state(
 2332        &r#"
 2333        one
 2334        two
 2335        three
 2336        four
 2337        five
 2338        six
 2339        ˇseven
 2340        eight
 2341        nineˇ
 2342        ten
 2343        "#
 2344        .unindent(),
 2345    );
 2346
 2347    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2348    cx.assert_editor_state(
 2349        &r#"
 2350        one
 2351        two
 2352        three
 2353        ˇfour
 2354        five
 2355        sixˇ
 2356        seven
 2357        eight
 2358        nine
 2359        ten
 2360        "#
 2361        .unindent(),
 2362    );
 2363
 2364    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2365    cx.assert_editor_state(
 2366        &r#"
 2367        ˇone
 2368        two
 2369        threeˇ
 2370        four
 2371        five
 2372        six
 2373        seven
 2374        eight
 2375        nine
 2376        ten
 2377        "#
 2378        .unindent(),
 2379    );
 2380
 2381    // Test select collapsing
 2382    cx.update_editor(|editor, window, cx| {
 2383        editor.move_page_down(&MovePageDown::default(), window, cx);
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386    });
 2387    cx.assert_editor_state(
 2388        &r#"
 2389        one
 2390        two
 2391        three
 2392        four
 2393        five
 2394        six
 2395        seven
 2396        eight
 2397        nine
 2398        ˇten
 2399        ˇ"#
 2400        .unindent(),
 2401    );
 2402}
 2403
 2404#[gpui::test]
 2405async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2406    init_test(cx, |_| {});
 2407    let mut cx = EditorTestContext::new(cx).await;
 2408    cx.set_state("one «two threeˇ» four");
 2409    cx.update_editor(|editor, window, cx| {
 2410        editor.delete_to_beginning_of_line(
 2411            &DeleteToBeginningOfLine {
 2412                stop_at_indent: false,
 2413            },
 2414            window,
 2415            cx,
 2416        );
 2417        assert_eq!(editor.text(cx), " four");
 2418    });
 2419}
 2420
 2421#[gpui::test]
 2422fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2423    init_test(cx, |_| {});
 2424
 2425    let editor = cx.add_window(|window, cx| {
 2426        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2427        build_editor(buffer.clone(), window, cx)
 2428    });
 2429
 2430    _ = editor.update(cx, |editor, window, cx| {
 2431        editor.change_selections(None, window, cx, |s| {
 2432            s.select_display_ranges([
 2433                // an empty selection - the preceding word fragment is deleted
 2434                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2435                // characters selected - they are deleted
 2436                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2437            ])
 2438        });
 2439        editor.delete_to_previous_word_start(
 2440            &DeleteToPreviousWordStart {
 2441                ignore_newlines: false,
 2442            },
 2443            window,
 2444            cx,
 2445        );
 2446        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2447    });
 2448
 2449    _ = editor.update(cx, |editor, window, cx| {
 2450        editor.change_selections(None, window, cx, |s| {
 2451            s.select_display_ranges([
 2452                // an empty selection - the following word fragment is deleted
 2453                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2454                // characters selected - they are deleted
 2455                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2456            ])
 2457        });
 2458        editor.delete_to_next_word_end(
 2459            &DeleteToNextWordEnd {
 2460                ignore_newlines: false,
 2461            },
 2462            window,
 2463            cx,
 2464        );
 2465        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2466    });
 2467}
 2468
 2469#[gpui::test]
 2470fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2471    init_test(cx, |_| {});
 2472
 2473    let editor = cx.add_window(|window, cx| {
 2474        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2475        build_editor(buffer.clone(), window, cx)
 2476    });
 2477    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2478        ignore_newlines: false,
 2479    };
 2480    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2481        ignore_newlines: true,
 2482    };
 2483
 2484    _ = editor.update(cx, |editor, window, cx| {
 2485        editor.change_selections(None, window, cx, |s| {
 2486            s.select_display_ranges([
 2487                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2488            ])
 2489        });
 2490        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2491        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2492        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2493        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2494        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2495        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2496        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2497        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2498        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2499        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2502    });
 2503}
 2504
 2505#[gpui::test]
 2506fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2507    init_test(cx, |_| {});
 2508
 2509    let editor = cx.add_window(|window, cx| {
 2510        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2511        build_editor(buffer.clone(), window, cx)
 2512    });
 2513    let del_to_next_word_end = DeleteToNextWordEnd {
 2514        ignore_newlines: false,
 2515    };
 2516    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2517        ignore_newlines: true,
 2518    };
 2519
 2520    _ = editor.update(cx, |editor, window, cx| {
 2521        editor.change_selections(None, window, cx, |s| {
 2522            s.select_display_ranges([
 2523                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2524            ])
 2525        });
 2526        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2527        assert_eq!(
 2528            editor.buffer.read(cx).read(cx).text(),
 2529            "one\n   two\nthree\n   four"
 2530        );
 2531        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2532        assert_eq!(
 2533            editor.buffer.read(cx).read(cx).text(),
 2534            "\n   two\nthree\n   four"
 2535        );
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2543        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2544        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2545        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2547    });
 2548}
 2549
 2550#[gpui::test]
 2551fn test_newline(cx: &mut TestAppContext) {
 2552    init_test(cx, |_| {});
 2553
 2554    let editor = cx.add_window(|window, cx| {
 2555        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2556        build_editor(buffer.clone(), window, cx)
 2557    });
 2558
 2559    _ = editor.update(cx, |editor, window, cx| {
 2560        editor.change_selections(None, window, cx, |s| {
 2561            s.select_display_ranges([
 2562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2563                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2564                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2565            ])
 2566        });
 2567
 2568        editor.newline(&Newline, window, cx);
 2569        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2570    });
 2571}
 2572
 2573#[gpui::test]
 2574fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2575    init_test(cx, |_| {});
 2576
 2577    let editor = cx.add_window(|window, cx| {
 2578        let buffer = MultiBuffer::build_simple(
 2579            "
 2580                a
 2581                b(
 2582                    X
 2583                )
 2584                c(
 2585                    X
 2586                )
 2587            "
 2588            .unindent()
 2589            .as_str(),
 2590            cx,
 2591        );
 2592        let mut editor = build_editor(buffer.clone(), window, cx);
 2593        editor.change_selections(None, window, cx, |s| {
 2594            s.select_ranges([
 2595                Point::new(2, 4)..Point::new(2, 5),
 2596                Point::new(5, 4)..Point::new(5, 5),
 2597            ])
 2598        });
 2599        editor
 2600    });
 2601
 2602    _ = editor.update(cx, |editor, window, cx| {
 2603        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2604        editor.buffer.update(cx, |buffer, cx| {
 2605            buffer.edit(
 2606                [
 2607                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2608                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2609                ],
 2610                None,
 2611                cx,
 2612            );
 2613            assert_eq!(
 2614                buffer.read(cx).text(),
 2615                "
 2616                    a
 2617                    b()
 2618                    c()
 2619                "
 2620                .unindent()
 2621            );
 2622        });
 2623        assert_eq!(
 2624            editor.selections.ranges(cx),
 2625            &[
 2626                Point::new(1, 2)..Point::new(1, 2),
 2627                Point::new(2, 2)..Point::new(2, 2),
 2628            ],
 2629        );
 2630
 2631        editor.newline(&Newline, window, cx);
 2632        assert_eq!(
 2633            editor.text(cx),
 2634            "
 2635                a
 2636                b(
 2637                )
 2638                c(
 2639                )
 2640            "
 2641            .unindent()
 2642        );
 2643
 2644        // The selections are moved after the inserted newlines
 2645        assert_eq!(
 2646            editor.selections.ranges(cx),
 2647            &[
 2648                Point::new(2, 0)..Point::new(2, 0),
 2649                Point::new(4, 0)..Point::new(4, 0),
 2650            ],
 2651        );
 2652    });
 2653}
 2654
 2655#[gpui::test]
 2656async fn test_newline_above(cx: &mut TestAppContext) {
 2657    init_test(cx, |settings| {
 2658        settings.defaults.tab_size = NonZeroU32::new(4)
 2659    });
 2660
 2661    let language = Arc::new(
 2662        Language::new(
 2663            LanguageConfig::default(),
 2664            Some(tree_sitter_rust::LANGUAGE.into()),
 2665        )
 2666        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2667        .unwrap(),
 2668    );
 2669
 2670    let mut cx = EditorTestContext::new(cx).await;
 2671    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2672    cx.set_state(indoc! {"
 2673        const a: ˇA = (
 2674 2675                «const_functionˇ»(ˇ),
 2676                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2677 2678        ˇ);ˇ
 2679    "});
 2680
 2681    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2682    cx.assert_editor_state(indoc! {"
 2683        ˇ
 2684        const a: A = (
 2685            ˇ
 2686            (
 2687                ˇ
 2688                ˇ
 2689                const_function(),
 2690                ˇ
 2691                ˇ
 2692                ˇ
 2693                ˇ
 2694                something_else,
 2695                ˇ
 2696            )
 2697            ˇ
 2698            ˇ
 2699        );
 2700    "});
 2701}
 2702
 2703#[gpui::test]
 2704async fn test_newline_below(cx: &mut TestAppContext) {
 2705    init_test(cx, |settings| {
 2706        settings.defaults.tab_size = NonZeroU32::new(4)
 2707    });
 2708
 2709    let language = Arc::new(
 2710        Language::new(
 2711            LanguageConfig::default(),
 2712            Some(tree_sitter_rust::LANGUAGE.into()),
 2713        )
 2714        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2715        .unwrap(),
 2716    );
 2717
 2718    let mut cx = EditorTestContext::new(cx).await;
 2719    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2720    cx.set_state(indoc! {"
 2721        const a: ˇA = (
 2722 2723                «const_functionˇ»(ˇ),
 2724                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2725 2726        ˇ);ˇ
 2727    "});
 2728
 2729    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2730    cx.assert_editor_state(indoc! {"
 2731        const a: A = (
 2732            ˇ
 2733            (
 2734                ˇ
 2735                const_function(),
 2736                ˇ
 2737                ˇ
 2738                something_else,
 2739                ˇ
 2740                ˇ
 2741                ˇ
 2742                ˇ
 2743            )
 2744            ˇ
 2745        );
 2746        ˇ
 2747        ˇ
 2748    "});
 2749}
 2750
 2751#[gpui::test]
 2752async fn test_newline_comments(cx: &mut TestAppContext) {
 2753    init_test(cx, |settings| {
 2754        settings.defaults.tab_size = NonZeroU32::new(4)
 2755    });
 2756
 2757    let language = Arc::new(Language::new(
 2758        LanguageConfig {
 2759            line_comments: vec!["//".into()],
 2760            ..LanguageConfig::default()
 2761        },
 2762        None,
 2763    ));
 2764    {
 2765        let mut cx = EditorTestContext::new(cx).await;
 2766        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2767        cx.set_state(indoc! {"
 2768        // Fooˇ
 2769    "});
 2770
 2771        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2772        cx.assert_editor_state(indoc! {"
 2773        // Foo
 2774        //ˇ
 2775    "});
 2776        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2777        cx.set_state(indoc! {"
 2778        ˇ// Foo
 2779    "});
 2780        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2781        cx.assert_editor_state(indoc! {"
 2782
 2783        ˇ// Foo
 2784    "});
 2785    }
 2786    // Ensure that comment continuations can be disabled.
 2787    update_test_language_settings(cx, |settings| {
 2788        settings.defaults.extend_comment_on_newline = Some(false);
 2789    });
 2790    let mut cx = EditorTestContext::new(cx).await;
 2791    cx.set_state(indoc! {"
 2792        // Fooˇ
 2793    "});
 2794    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2795    cx.assert_editor_state(indoc! {"
 2796        // Foo
 2797        ˇ
 2798    "});
 2799}
 2800
 2801#[gpui::test]
 2802fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2803    init_test(cx, |_| {});
 2804
 2805    let editor = cx.add_window(|window, cx| {
 2806        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2807        let mut editor = build_editor(buffer.clone(), window, cx);
 2808        editor.change_selections(None, window, cx, |s| {
 2809            s.select_ranges([3..4, 11..12, 19..20])
 2810        });
 2811        editor
 2812    });
 2813
 2814    _ = editor.update(cx, |editor, window, cx| {
 2815        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2816        editor.buffer.update(cx, |buffer, cx| {
 2817            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2818            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2819        });
 2820        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2821
 2822        editor.insert("Z", window, cx);
 2823        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2824
 2825        // The selections are moved after the inserted characters
 2826        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2827    });
 2828}
 2829
 2830#[gpui::test]
 2831async fn test_tab(cx: &mut TestAppContext) {
 2832    init_test(cx, |settings| {
 2833        settings.defaults.tab_size = NonZeroU32::new(3)
 2834    });
 2835
 2836    let mut cx = EditorTestContext::new(cx).await;
 2837    cx.set_state(indoc! {"
 2838        ˇabˇc
 2839        ˇ🏀ˇ🏀ˇefg
 2840 2841    "});
 2842    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2843    cx.assert_editor_state(indoc! {"
 2844           ˇab ˇc
 2845           ˇ🏀  ˇ🏀  ˇefg
 2846        d  ˇ
 2847    "});
 2848
 2849    cx.set_state(indoc! {"
 2850        a
 2851        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2852    "});
 2853    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2854    cx.assert_editor_state(indoc! {"
 2855        a
 2856           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2857    "});
 2858}
 2859
 2860#[gpui::test]
 2861async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 2862    init_test(cx, |_| {});
 2863
 2864    let mut cx = EditorTestContext::new(cx).await;
 2865    let language = Arc::new(
 2866        Language::new(
 2867            LanguageConfig::default(),
 2868            Some(tree_sitter_rust::LANGUAGE.into()),
 2869        )
 2870        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2871        .unwrap(),
 2872    );
 2873    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2874
 2875    // cursors that are already at the suggested indent level insert
 2876    // a soft tab. cursors that are to the left of the suggested indent
 2877    // auto-indent their line.
 2878    cx.set_state(indoc! {"
 2879        ˇ
 2880        const a: B = (
 2881            c(
 2882                d(
 2883        ˇ
 2884                )
 2885        ˇ
 2886        ˇ    )
 2887        );
 2888    "});
 2889    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2890    cx.assert_editor_state(indoc! {"
 2891            ˇ
 2892        const a: B = (
 2893            c(
 2894                d(
 2895                    ˇ
 2896                )
 2897                ˇ
 2898            ˇ)
 2899        );
 2900    "});
 2901
 2902    // handle auto-indent when there are multiple cursors on the same line
 2903    cx.set_state(indoc! {"
 2904        const a: B = (
 2905            c(
 2906        ˇ    ˇ
 2907        ˇ    )
 2908        );
 2909    "});
 2910    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2911    cx.assert_editor_state(indoc! {"
 2912        const a: B = (
 2913            c(
 2914                ˇ
 2915            ˇ)
 2916        );
 2917    "});
 2918}
 2919
 2920#[gpui::test]
 2921async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 2922    init_test(cx, |settings| {
 2923        settings.defaults.tab_size = NonZeroU32::new(3)
 2924    });
 2925
 2926    let mut cx = EditorTestContext::new(cx).await;
 2927    cx.set_state(indoc! {"
 2928         ˇ
 2929        \t ˇ
 2930        \t  ˇ
 2931        \t   ˇ
 2932         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 2933    "});
 2934
 2935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2936    cx.assert_editor_state(indoc! {"
 2937           ˇ
 2938        \t   ˇ
 2939        \t   ˇ
 2940        \t      ˇ
 2941         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 2942    "});
 2943}
 2944
 2945#[gpui::test]
 2946async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 2947    init_test(cx, |settings| {
 2948        settings.defaults.tab_size = NonZeroU32::new(4)
 2949    });
 2950
 2951    let language = Arc::new(
 2952        Language::new(
 2953            LanguageConfig::default(),
 2954            Some(tree_sitter_rust::LANGUAGE.into()),
 2955        )
 2956        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 2957        .unwrap(),
 2958    );
 2959
 2960    let mut cx = EditorTestContext::new(cx).await;
 2961    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2962    cx.set_state(indoc! {"
 2963        fn a() {
 2964            if b {
 2965        \t ˇc
 2966            }
 2967        }
 2968    "});
 2969
 2970    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2971    cx.assert_editor_state(indoc! {"
 2972        fn a() {
 2973            if b {
 2974                ˇc
 2975            }
 2976        }
 2977    "});
 2978}
 2979
 2980#[gpui::test]
 2981async fn test_indent_outdent(cx: &mut TestAppContext) {
 2982    init_test(cx, |settings| {
 2983        settings.defaults.tab_size = NonZeroU32::new(4);
 2984    });
 2985
 2986    let mut cx = EditorTestContext::new(cx).await;
 2987
 2988    cx.set_state(indoc! {"
 2989          «oneˇ» «twoˇ»
 2990        three
 2991         four
 2992    "});
 2993    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2994    cx.assert_editor_state(indoc! {"
 2995            «oneˇ» «twoˇ»
 2996        three
 2997         four
 2998    "});
 2999
 3000    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3001    cx.assert_editor_state(indoc! {"
 3002        «oneˇ» «twoˇ»
 3003        three
 3004         four
 3005    "});
 3006
 3007    // select across line ending
 3008    cx.set_state(indoc! {"
 3009        one two
 3010        t«hree
 3011        ˇ» four
 3012    "});
 3013    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3014    cx.assert_editor_state(indoc! {"
 3015        one two
 3016            t«hree
 3017        ˇ» four
 3018    "});
 3019
 3020    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3021    cx.assert_editor_state(indoc! {"
 3022        one two
 3023        t«hree
 3024        ˇ» four
 3025    "});
 3026
 3027    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3028    cx.set_state(indoc! {"
 3029        one two
 3030        ˇthree
 3031            four
 3032    "});
 3033    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3034    cx.assert_editor_state(indoc! {"
 3035        one two
 3036            ˇthree
 3037            four
 3038    "});
 3039
 3040    cx.set_state(indoc! {"
 3041        one two
 3042        ˇ    three
 3043            four
 3044    "});
 3045    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3046    cx.assert_editor_state(indoc! {"
 3047        one two
 3048        ˇthree
 3049            four
 3050    "});
 3051}
 3052
 3053#[gpui::test]
 3054async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3055    init_test(cx, |settings| {
 3056        settings.defaults.hard_tabs = Some(true);
 3057    });
 3058
 3059    let mut cx = EditorTestContext::new(cx).await;
 3060
 3061    // select two ranges on one line
 3062    cx.set_state(indoc! {"
 3063        «oneˇ» «twoˇ»
 3064        three
 3065        four
 3066    "});
 3067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3068    cx.assert_editor_state(indoc! {"
 3069        \t«oneˇ» «twoˇ»
 3070        three
 3071        four
 3072    "});
 3073    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3074    cx.assert_editor_state(indoc! {"
 3075        \t\t«oneˇ» «twoˇ»
 3076        three
 3077        four
 3078    "});
 3079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3080    cx.assert_editor_state(indoc! {"
 3081        \t«oneˇ» «twoˇ»
 3082        three
 3083        four
 3084    "});
 3085    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3086    cx.assert_editor_state(indoc! {"
 3087        «oneˇ» «twoˇ»
 3088        three
 3089        four
 3090    "});
 3091
 3092    // select across a line ending
 3093    cx.set_state(indoc! {"
 3094        one two
 3095        t«hree
 3096        ˇ»four
 3097    "});
 3098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3099    cx.assert_editor_state(indoc! {"
 3100        one two
 3101        \tt«hree
 3102        ˇ»four
 3103    "});
 3104    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3105    cx.assert_editor_state(indoc! {"
 3106        one two
 3107        \t\tt«hree
 3108        ˇ»four
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        one two
 3113        \tt«hree
 3114        ˇ»four
 3115    "});
 3116    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3117    cx.assert_editor_state(indoc! {"
 3118        one two
 3119        t«hree
 3120        ˇ»four
 3121    "});
 3122
 3123    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3124    cx.set_state(indoc! {"
 3125        one two
 3126        ˇthree
 3127        four
 3128    "});
 3129    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3130    cx.assert_editor_state(indoc! {"
 3131        one two
 3132        ˇthree
 3133        four
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        one two
 3138        \tˇthree
 3139        four
 3140    "});
 3141    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3142    cx.assert_editor_state(indoc! {"
 3143        one two
 3144        ˇthree
 3145        four
 3146    "});
 3147}
 3148
 3149#[gpui::test]
 3150fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3151    init_test(cx, |settings| {
 3152        settings.languages.extend([
 3153            (
 3154                "TOML".into(),
 3155                LanguageSettingsContent {
 3156                    tab_size: NonZeroU32::new(2),
 3157                    ..Default::default()
 3158                },
 3159            ),
 3160            (
 3161                "Rust".into(),
 3162                LanguageSettingsContent {
 3163                    tab_size: NonZeroU32::new(4),
 3164                    ..Default::default()
 3165                },
 3166            ),
 3167        ]);
 3168    });
 3169
 3170    let toml_language = Arc::new(Language::new(
 3171        LanguageConfig {
 3172            name: "TOML".into(),
 3173            ..Default::default()
 3174        },
 3175        None,
 3176    ));
 3177    let rust_language = Arc::new(Language::new(
 3178        LanguageConfig {
 3179            name: "Rust".into(),
 3180            ..Default::default()
 3181        },
 3182        None,
 3183    ));
 3184
 3185    let toml_buffer =
 3186        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3187    let rust_buffer =
 3188        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3189    let multibuffer = cx.new(|cx| {
 3190        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3191        multibuffer.push_excerpts(
 3192            toml_buffer.clone(),
 3193            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3194            cx,
 3195        );
 3196        multibuffer.push_excerpts(
 3197            rust_buffer.clone(),
 3198            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3199            cx,
 3200        );
 3201        multibuffer
 3202    });
 3203
 3204    cx.add_window(|window, cx| {
 3205        let mut editor = build_editor(multibuffer, window, cx);
 3206
 3207        assert_eq!(
 3208            editor.text(cx),
 3209            indoc! {"
 3210                a = 1
 3211                b = 2
 3212
 3213                const c: usize = 3;
 3214            "}
 3215        );
 3216
 3217        select_ranges(
 3218            &mut editor,
 3219            indoc! {"
 3220                «aˇ» = 1
 3221                b = 2
 3222
 3223                «const c:ˇ» usize = 3;
 3224            "},
 3225            window,
 3226            cx,
 3227        );
 3228
 3229        editor.tab(&Tab, window, cx);
 3230        assert_text_with_selections(
 3231            &mut editor,
 3232            indoc! {"
 3233                  «aˇ» = 1
 3234                b = 2
 3235
 3236                    «const c:ˇ» usize = 3;
 3237            "},
 3238            cx,
 3239        );
 3240        editor.backtab(&Backtab, window, cx);
 3241        assert_text_with_selections(
 3242            &mut editor,
 3243            indoc! {"
 3244                «aˇ» = 1
 3245                b = 2
 3246
 3247                «const c:ˇ» usize = 3;
 3248            "},
 3249            cx,
 3250        );
 3251
 3252        editor
 3253    });
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_backspace(cx: &mut TestAppContext) {
 3258    init_test(cx, |_| {});
 3259
 3260    let mut cx = EditorTestContext::new(cx).await;
 3261
 3262    // Basic backspace
 3263    cx.set_state(indoc! {"
 3264        onˇe two three
 3265        fou«rˇ» five six
 3266        seven «ˇeight nine
 3267        »ten
 3268    "});
 3269    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3270    cx.assert_editor_state(indoc! {"
 3271        oˇe two three
 3272        fouˇ five six
 3273        seven ˇten
 3274    "});
 3275
 3276    // Test backspace inside and around indents
 3277    cx.set_state(indoc! {"
 3278        zero
 3279            ˇone
 3280                ˇtwo
 3281            ˇ ˇ ˇ  three
 3282        ˇ  ˇ  four
 3283    "});
 3284    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3285    cx.assert_editor_state(indoc! {"
 3286        zero
 3287        ˇone
 3288            ˇtwo
 3289        ˇ  threeˇ  four
 3290    "});
 3291}
 3292
 3293#[gpui::test]
 3294async fn test_delete(cx: &mut TestAppContext) {
 3295    init_test(cx, |_| {});
 3296
 3297    let mut cx = EditorTestContext::new(cx).await;
 3298    cx.set_state(indoc! {"
 3299        onˇe two three
 3300        fou«rˇ» five six
 3301        seven «ˇeight nine
 3302        »ten
 3303    "});
 3304    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3305    cx.assert_editor_state(indoc! {"
 3306        onˇ two three
 3307        fouˇ five six
 3308        seven ˇten
 3309    "});
 3310}
 3311
 3312#[gpui::test]
 3313fn test_delete_line(cx: &mut TestAppContext) {
 3314    init_test(cx, |_| {});
 3315
 3316    let editor = cx.add_window(|window, cx| {
 3317        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3318        build_editor(buffer, window, cx)
 3319    });
 3320    _ = editor.update(cx, |editor, window, cx| {
 3321        editor.change_selections(None, window, cx, |s| {
 3322            s.select_display_ranges([
 3323                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3324                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3325                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3326            ])
 3327        });
 3328        editor.delete_line(&DeleteLine, window, cx);
 3329        assert_eq!(editor.display_text(cx), "ghi");
 3330        assert_eq!(
 3331            editor.selections.display_ranges(cx),
 3332            vec![
 3333                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3334                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3335            ]
 3336        );
 3337    });
 3338
 3339    let editor = cx.add_window(|window, cx| {
 3340        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3341        build_editor(buffer, window, cx)
 3342    });
 3343    _ = editor.update(cx, |editor, window, cx| {
 3344        editor.change_selections(None, window, cx, |s| {
 3345            s.select_display_ranges([
 3346                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3347            ])
 3348        });
 3349        editor.delete_line(&DeleteLine, window, cx);
 3350        assert_eq!(editor.display_text(cx), "ghi\n");
 3351        assert_eq!(
 3352            editor.selections.display_ranges(cx),
 3353            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3354        );
 3355    });
 3356}
 3357
 3358#[gpui::test]
 3359fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3360    init_test(cx, |_| {});
 3361
 3362    cx.add_window(|window, cx| {
 3363        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3364        let mut editor = build_editor(buffer.clone(), window, cx);
 3365        let buffer = buffer.read(cx).as_singleton().unwrap();
 3366
 3367        assert_eq!(
 3368            editor.selections.ranges::<Point>(cx),
 3369            &[Point::new(0, 0)..Point::new(0, 0)]
 3370        );
 3371
 3372        // When on single line, replace newline at end by space
 3373        editor.join_lines(&JoinLines, window, cx);
 3374        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3375        assert_eq!(
 3376            editor.selections.ranges::<Point>(cx),
 3377            &[Point::new(0, 3)..Point::new(0, 3)]
 3378        );
 3379
 3380        // When multiple lines are selected, remove newlines that are spanned by the selection
 3381        editor.change_selections(None, window, cx, |s| {
 3382            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3383        });
 3384        editor.join_lines(&JoinLines, window, cx);
 3385        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3386        assert_eq!(
 3387            editor.selections.ranges::<Point>(cx),
 3388            &[Point::new(0, 11)..Point::new(0, 11)]
 3389        );
 3390
 3391        // Undo should be transactional
 3392        editor.undo(&Undo, window, cx);
 3393        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3394        assert_eq!(
 3395            editor.selections.ranges::<Point>(cx),
 3396            &[Point::new(0, 5)..Point::new(2, 2)]
 3397        );
 3398
 3399        // When joining an empty line don't insert a space
 3400        editor.change_selections(None, window, cx, |s| {
 3401            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3402        });
 3403        editor.join_lines(&JoinLines, window, cx);
 3404        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3405        assert_eq!(
 3406            editor.selections.ranges::<Point>(cx),
 3407            [Point::new(2, 3)..Point::new(2, 3)]
 3408        );
 3409
 3410        // We can remove trailing newlines
 3411        editor.join_lines(&JoinLines, window, cx);
 3412        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3413        assert_eq!(
 3414            editor.selections.ranges::<Point>(cx),
 3415            [Point::new(2, 3)..Point::new(2, 3)]
 3416        );
 3417
 3418        // We don't blow up on the last line
 3419        editor.join_lines(&JoinLines, window, cx);
 3420        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3421        assert_eq!(
 3422            editor.selections.ranges::<Point>(cx),
 3423            [Point::new(2, 3)..Point::new(2, 3)]
 3424        );
 3425
 3426        // reset to test indentation
 3427        editor.buffer.update(cx, |buffer, cx| {
 3428            buffer.edit(
 3429                [
 3430                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3431                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3432                ],
 3433                None,
 3434                cx,
 3435            )
 3436        });
 3437
 3438        // We remove any leading spaces
 3439        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3440        editor.change_selections(None, window, cx, |s| {
 3441            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3442        });
 3443        editor.join_lines(&JoinLines, window, cx);
 3444        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3445
 3446        // We don't insert a space for a line containing only spaces
 3447        editor.join_lines(&JoinLines, window, cx);
 3448        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3449
 3450        // We ignore any leading tabs
 3451        editor.join_lines(&JoinLines, window, cx);
 3452        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3453
 3454        editor
 3455    });
 3456}
 3457
 3458#[gpui::test]
 3459fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3460    init_test(cx, |_| {});
 3461
 3462    cx.add_window(|window, cx| {
 3463        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3464        let mut editor = build_editor(buffer.clone(), window, cx);
 3465        let buffer = buffer.read(cx).as_singleton().unwrap();
 3466
 3467        editor.change_selections(None, window, cx, |s| {
 3468            s.select_ranges([
 3469                Point::new(0, 2)..Point::new(1, 1),
 3470                Point::new(1, 2)..Point::new(1, 2),
 3471                Point::new(3, 1)..Point::new(3, 2),
 3472            ])
 3473        });
 3474
 3475        editor.join_lines(&JoinLines, window, cx);
 3476        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3477
 3478        assert_eq!(
 3479            editor.selections.ranges::<Point>(cx),
 3480            [
 3481                Point::new(0, 7)..Point::new(0, 7),
 3482                Point::new(1, 3)..Point::new(1, 3)
 3483            ]
 3484        );
 3485        editor
 3486    });
 3487}
 3488
 3489#[gpui::test]
 3490async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3491    init_test(cx, |_| {});
 3492
 3493    let mut cx = EditorTestContext::new(cx).await;
 3494
 3495    let diff_base = r#"
 3496        Line 0
 3497        Line 1
 3498        Line 2
 3499        Line 3
 3500        "#
 3501    .unindent();
 3502
 3503    cx.set_state(
 3504        &r#"
 3505        ˇLine 0
 3506        Line 1
 3507        Line 2
 3508        Line 3
 3509        "#
 3510        .unindent(),
 3511    );
 3512
 3513    cx.set_head_text(&diff_base);
 3514    executor.run_until_parked();
 3515
 3516    // Join lines
 3517    cx.update_editor(|editor, window, cx| {
 3518        editor.join_lines(&JoinLines, window, cx);
 3519    });
 3520    executor.run_until_parked();
 3521
 3522    cx.assert_editor_state(
 3523        &r#"
 3524        Line 0ˇ Line 1
 3525        Line 2
 3526        Line 3
 3527        "#
 3528        .unindent(),
 3529    );
 3530    // Join again
 3531    cx.update_editor(|editor, window, cx| {
 3532        editor.join_lines(&JoinLines, window, cx);
 3533    });
 3534    executor.run_until_parked();
 3535
 3536    cx.assert_editor_state(
 3537        &r#"
 3538        Line 0 Line 1ˇ Line 2
 3539        Line 3
 3540        "#
 3541        .unindent(),
 3542    );
 3543}
 3544
 3545#[gpui::test]
 3546async fn test_custom_newlines_cause_no_false_positive_diffs(
 3547    executor: BackgroundExecutor,
 3548    cx: &mut TestAppContext,
 3549) {
 3550    init_test(cx, |_| {});
 3551    let mut cx = EditorTestContext::new(cx).await;
 3552    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3553    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3554    executor.run_until_parked();
 3555
 3556    cx.update_editor(|editor, window, cx| {
 3557        let snapshot = editor.snapshot(window, cx);
 3558        assert_eq!(
 3559            snapshot
 3560                .buffer_snapshot
 3561                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3562                .collect::<Vec<_>>(),
 3563            Vec::new(),
 3564            "Should not have any diffs for files with custom newlines"
 3565        );
 3566    });
 3567}
 3568
 3569#[gpui::test]
 3570async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3571    init_test(cx, |_| {});
 3572
 3573    let mut cx = EditorTestContext::new(cx).await;
 3574
 3575    // Test sort_lines_case_insensitive()
 3576    cx.set_state(indoc! {"
 3577        «z
 3578        y
 3579        x
 3580        Z
 3581        Y
 3582        Xˇ»
 3583    "});
 3584    cx.update_editor(|e, window, cx| {
 3585        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3586    });
 3587    cx.assert_editor_state(indoc! {"
 3588        «x
 3589        X
 3590        y
 3591        Y
 3592        z
 3593        Zˇ»
 3594    "});
 3595
 3596    // Test reverse_lines()
 3597    cx.set_state(indoc! {"
 3598        «5
 3599        4
 3600        3
 3601        2
 3602        1ˇ»
 3603    "});
 3604    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3605    cx.assert_editor_state(indoc! {"
 3606        «1
 3607        2
 3608        3
 3609        4
 3610        5ˇ»
 3611    "});
 3612
 3613    // Skip testing shuffle_line()
 3614
 3615    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3616    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3617
 3618    // Don't manipulate when cursor is on single line, but expand the selection
 3619    cx.set_state(indoc! {"
 3620        ddˇdd
 3621        ccc
 3622        bb
 3623        a
 3624    "});
 3625    cx.update_editor(|e, window, cx| {
 3626        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3627    });
 3628    cx.assert_editor_state(indoc! {"
 3629        «ddddˇ»
 3630        ccc
 3631        bb
 3632        a
 3633    "});
 3634
 3635    // Basic manipulate case
 3636    // Start selection moves to column 0
 3637    // End of selection shrinks to fit shorter line
 3638    cx.set_state(indoc! {"
 3639        dd«d
 3640        ccc
 3641        bb
 3642        aaaaaˇ»
 3643    "});
 3644    cx.update_editor(|e, window, cx| {
 3645        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3646    });
 3647    cx.assert_editor_state(indoc! {"
 3648        «aaaaa
 3649        bb
 3650        ccc
 3651        dddˇ»
 3652    "});
 3653
 3654    // Manipulate case with newlines
 3655    cx.set_state(indoc! {"
 3656        dd«d
 3657        ccc
 3658
 3659        bb
 3660        aaaaa
 3661
 3662        ˇ»
 3663    "});
 3664    cx.update_editor(|e, window, cx| {
 3665        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3666    });
 3667    cx.assert_editor_state(indoc! {"
 3668        «
 3669
 3670        aaaaa
 3671        bb
 3672        ccc
 3673        dddˇ»
 3674
 3675    "});
 3676
 3677    // Adding new line
 3678    cx.set_state(indoc! {"
 3679        aa«a
 3680        bbˇ»b
 3681    "});
 3682    cx.update_editor(|e, window, cx| {
 3683        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 3684    });
 3685    cx.assert_editor_state(indoc! {"
 3686        «aaa
 3687        bbb
 3688        added_lineˇ»
 3689    "});
 3690
 3691    // Removing line
 3692    cx.set_state(indoc! {"
 3693        aa«a
 3694        bbbˇ»
 3695    "});
 3696    cx.update_editor(|e, window, cx| {
 3697        e.manipulate_lines(window, cx, |lines| {
 3698            lines.pop();
 3699        })
 3700    });
 3701    cx.assert_editor_state(indoc! {"
 3702        «aaaˇ»
 3703    "});
 3704
 3705    // Removing all lines
 3706    cx.set_state(indoc! {"
 3707        aa«a
 3708        bbbˇ»
 3709    "});
 3710    cx.update_editor(|e, window, cx| {
 3711        e.manipulate_lines(window, cx, |lines| {
 3712            lines.drain(..);
 3713        })
 3714    });
 3715    cx.assert_editor_state(indoc! {"
 3716        ˇ
 3717    "});
 3718}
 3719
 3720#[gpui::test]
 3721async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 3722    init_test(cx, |_| {});
 3723
 3724    let mut cx = EditorTestContext::new(cx).await;
 3725
 3726    // Consider continuous selection as single selection
 3727    cx.set_state(indoc! {"
 3728        Aaa«aa
 3729        cˇ»c«c
 3730        bb
 3731        aaaˇ»aa
 3732    "});
 3733    cx.update_editor(|e, window, cx| {
 3734        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3735    });
 3736    cx.assert_editor_state(indoc! {"
 3737        «Aaaaa
 3738        ccc
 3739        bb
 3740        aaaaaˇ»
 3741    "});
 3742
 3743    cx.set_state(indoc! {"
 3744        Aaa«aa
 3745        cˇ»c«c
 3746        bb
 3747        aaaˇ»aa
 3748    "});
 3749    cx.update_editor(|e, window, cx| {
 3750        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3751    });
 3752    cx.assert_editor_state(indoc! {"
 3753        «Aaaaa
 3754        ccc
 3755        bbˇ»
 3756    "});
 3757
 3758    // Consider non continuous selection as distinct dedup operations
 3759    cx.set_state(indoc! {"
 3760        «aaaaa
 3761        bb
 3762        aaaaa
 3763        aaaaaˇ»
 3764
 3765        aaa«aaˇ»
 3766    "});
 3767    cx.update_editor(|e, window, cx| {
 3768        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3769    });
 3770    cx.assert_editor_state(indoc! {"
 3771        «aaaaa
 3772        bbˇ»
 3773
 3774        «aaaaaˇ»
 3775    "});
 3776}
 3777
 3778#[gpui::test]
 3779async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 3780    init_test(cx, |_| {});
 3781
 3782    let mut cx = EditorTestContext::new(cx).await;
 3783
 3784    cx.set_state(indoc! {"
 3785        «Aaa
 3786        aAa
 3787        Aaaˇ»
 3788    "});
 3789    cx.update_editor(|e, window, cx| {
 3790        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3791    });
 3792    cx.assert_editor_state(indoc! {"
 3793        «Aaa
 3794        aAaˇ»
 3795    "});
 3796
 3797    cx.set_state(indoc! {"
 3798        «Aaa
 3799        aAa
 3800        aaAˇ»
 3801    "});
 3802    cx.update_editor(|e, window, cx| {
 3803        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3804    });
 3805    cx.assert_editor_state(indoc! {"
 3806        «Aaaˇ»
 3807    "});
 3808}
 3809
 3810#[gpui::test]
 3811async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 3812    init_test(cx, |_| {});
 3813
 3814    let mut cx = EditorTestContext::new(cx).await;
 3815
 3816    // Manipulate with multiple selections on a single line
 3817    cx.set_state(indoc! {"
 3818        dd«dd
 3819        cˇ»c«c
 3820        bb
 3821        aaaˇ»aa
 3822    "});
 3823    cx.update_editor(|e, window, cx| {
 3824        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3825    });
 3826    cx.assert_editor_state(indoc! {"
 3827        «aaaaa
 3828        bb
 3829        ccc
 3830        ddddˇ»
 3831    "});
 3832
 3833    // Manipulate with multiple disjoin selections
 3834    cx.set_state(indoc! {"
 3835 3836        4
 3837        3
 3838        2
 3839        1ˇ»
 3840
 3841        dd«dd
 3842        ccc
 3843        bb
 3844        aaaˇ»aa
 3845    "});
 3846    cx.update_editor(|e, window, cx| {
 3847        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3848    });
 3849    cx.assert_editor_state(indoc! {"
 3850        «1
 3851        2
 3852        3
 3853        4
 3854        5ˇ»
 3855
 3856        «aaaaa
 3857        bb
 3858        ccc
 3859        ddddˇ»
 3860    "});
 3861
 3862    // Adding lines on each selection
 3863    cx.set_state(indoc! {"
 3864 3865        1ˇ»
 3866
 3867        bb«bb
 3868        aaaˇ»aa
 3869    "});
 3870    cx.update_editor(|e, window, cx| {
 3871        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 3872    });
 3873    cx.assert_editor_state(indoc! {"
 3874        «2
 3875        1
 3876        added lineˇ»
 3877
 3878        «bbbb
 3879        aaaaa
 3880        added lineˇ»
 3881    "});
 3882
 3883    // Removing lines on each selection
 3884    cx.set_state(indoc! {"
 3885 3886        1ˇ»
 3887
 3888        bb«bb
 3889        aaaˇ»aa
 3890    "});
 3891    cx.update_editor(|e, window, cx| {
 3892        e.manipulate_lines(window, cx, |lines| {
 3893            lines.pop();
 3894        })
 3895    });
 3896    cx.assert_editor_state(indoc! {"
 3897        «2ˇ»
 3898
 3899        «bbbbˇ»
 3900    "});
 3901}
 3902
 3903#[gpui::test]
 3904async fn test_toggle_case(cx: &mut TestAppContext) {
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908
 3909    // If all lower case -> upper case
 3910    cx.set_state(indoc! {"
 3911        «hello worldˇ»
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        «HELLO WORLDˇ»
 3916    "});
 3917
 3918    // If all upper case -> lower case
 3919    cx.set_state(indoc! {"
 3920        «HELLO WORLDˇ»
 3921    "});
 3922    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3923    cx.assert_editor_state(indoc! {"
 3924        «hello worldˇ»
 3925    "});
 3926
 3927    // If any upper case characters are identified -> lower case
 3928    // This matches JetBrains IDEs
 3929    cx.set_state(indoc! {"
 3930        «hEllo worldˇ»
 3931    "});
 3932    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 3933    cx.assert_editor_state(indoc! {"
 3934        «hello worldˇ»
 3935    "});
 3936}
 3937
 3938#[gpui::test]
 3939async fn test_manipulate_text(cx: &mut TestAppContext) {
 3940    init_test(cx, |_| {});
 3941
 3942    let mut cx = EditorTestContext::new(cx).await;
 3943
 3944    // Test convert_to_upper_case()
 3945    cx.set_state(indoc! {"
 3946        «hello worldˇ»
 3947    "});
 3948    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3949    cx.assert_editor_state(indoc! {"
 3950        «HELLO WORLDˇ»
 3951    "});
 3952
 3953    // Test convert_to_lower_case()
 3954    cx.set_state(indoc! {"
 3955        «HELLO WORLDˇ»
 3956    "});
 3957    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 3958    cx.assert_editor_state(indoc! {"
 3959        «hello worldˇ»
 3960    "});
 3961
 3962    // Test multiple line, single selection case
 3963    cx.set_state(indoc! {"
 3964        «The quick brown
 3965        fox jumps over
 3966        the lazy dogˇ»
 3967    "});
 3968    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 3969    cx.assert_editor_state(indoc! {"
 3970        «The Quick Brown
 3971        Fox Jumps Over
 3972        The Lazy Dogˇ»
 3973    "});
 3974
 3975    // Test multiple line, single selection case
 3976    cx.set_state(indoc! {"
 3977        «The quick brown
 3978        fox jumps over
 3979        the lazy dogˇ»
 3980    "});
 3981    cx.update_editor(|e, window, cx| {
 3982        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 3983    });
 3984    cx.assert_editor_state(indoc! {"
 3985        «TheQuickBrown
 3986        FoxJumpsOver
 3987        TheLazyDogˇ»
 3988    "});
 3989
 3990    // From here on out, test more complex cases of manipulate_text()
 3991
 3992    // Test no selection case - should affect words cursors are in
 3993    // Cursor at beginning, middle, and end of word
 3994    cx.set_state(indoc! {"
 3995        ˇhello big beauˇtiful worldˇ
 3996    "});
 3997    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3998    cx.assert_editor_state(indoc! {"
 3999        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4000    "});
 4001
 4002    // Test multiple selections on a single line and across multiple lines
 4003    cx.set_state(indoc! {"
 4004        «Theˇ» quick «brown
 4005        foxˇ» jumps «overˇ»
 4006        the «lazyˇ» dog
 4007    "});
 4008    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4009    cx.assert_editor_state(indoc! {"
 4010        «THEˇ» quick «BROWN
 4011        FOXˇ» jumps «OVERˇ»
 4012        the «LAZYˇ» dog
 4013    "});
 4014
 4015    // Test case where text length grows
 4016    cx.set_state(indoc! {"
 4017        «tschüߡ»
 4018    "});
 4019    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4020    cx.assert_editor_state(indoc! {"
 4021        «TSCHÜSSˇ»
 4022    "});
 4023
 4024    // Test to make sure we don't crash when text shrinks
 4025    cx.set_state(indoc! {"
 4026        aaa_bbbˇ
 4027    "});
 4028    cx.update_editor(|e, window, cx| {
 4029        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4030    });
 4031    cx.assert_editor_state(indoc! {"
 4032        «aaaBbbˇ»
 4033    "});
 4034
 4035    // Test to make sure we all aware of the fact that each word can grow and shrink
 4036    // Final selections should be aware of this fact
 4037    cx.set_state(indoc! {"
 4038        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4039    "});
 4040    cx.update_editor(|e, window, cx| {
 4041        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4042    });
 4043    cx.assert_editor_state(indoc! {"
 4044        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4045    "});
 4046
 4047    cx.set_state(indoc! {"
 4048        «hElLo, WoRld!ˇ»
 4049    "});
 4050    cx.update_editor(|e, window, cx| {
 4051        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4052    });
 4053    cx.assert_editor_state(indoc! {"
 4054        «HeLlO, wOrLD!ˇ»
 4055    "});
 4056}
 4057
 4058#[gpui::test]
 4059fn test_duplicate_line(cx: &mut TestAppContext) {
 4060    init_test(cx, |_| {});
 4061
 4062    let editor = cx.add_window(|window, cx| {
 4063        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4064        build_editor(buffer, window, cx)
 4065    });
 4066    _ = editor.update(cx, |editor, window, cx| {
 4067        editor.change_selections(None, window, cx, |s| {
 4068            s.select_display_ranges([
 4069                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4070                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4071                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4072                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4073            ])
 4074        });
 4075        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4076        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4077        assert_eq!(
 4078            editor.selections.display_ranges(cx),
 4079            vec![
 4080                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4081                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4082                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4084            ]
 4085        );
 4086    });
 4087
 4088    let editor = cx.add_window(|window, cx| {
 4089        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4090        build_editor(buffer, window, cx)
 4091    });
 4092    _ = editor.update(cx, |editor, window, cx| {
 4093        editor.change_selections(None, window, cx, |s| {
 4094            s.select_display_ranges([
 4095                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4096                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4097            ])
 4098        });
 4099        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4100        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4101        assert_eq!(
 4102            editor.selections.display_ranges(cx),
 4103            vec![
 4104                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4105                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4106            ]
 4107        );
 4108    });
 4109
 4110    // With `move_upwards` the selections stay in place, except for
 4111    // the lines inserted above them
 4112    let editor = cx.add_window(|window, cx| {
 4113        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4114        build_editor(buffer, window, cx)
 4115    });
 4116    _ = editor.update(cx, |editor, window, cx| {
 4117        editor.change_selections(None, window, cx, |s| {
 4118            s.select_display_ranges([
 4119                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4120                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4121                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4122                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4123            ])
 4124        });
 4125        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4126        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4127        assert_eq!(
 4128            editor.selections.display_ranges(cx),
 4129            vec![
 4130                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4131                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4132                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4133                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4134            ]
 4135        );
 4136    });
 4137
 4138    let editor = cx.add_window(|window, cx| {
 4139        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4140        build_editor(buffer, window, cx)
 4141    });
 4142    _ = editor.update(cx, |editor, window, cx| {
 4143        editor.change_selections(None, window, cx, |s| {
 4144            s.select_display_ranges([
 4145                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4146                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4147            ])
 4148        });
 4149        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4150        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4151        assert_eq!(
 4152            editor.selections.display_ranges(cx),
 4153            vec![
 4154                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4155                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4156            ]
 4157        );
 4158    });
 4159
 4160    let editor = cx.add_window(|window, cx| {
 4161        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4162        build_editor(buffer, window, cx)
 4163    });
 4164    _ = editor.update(cx, |editor, window, cx| {
 4165        editor.change_selections(None, window, cx, |s| {
 4166            s.select_display_ranges([
 4167                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4168                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4169            ])
 4170        });
 4171        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4172        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4173        assert_eq!(
 4174            editor.selections.display_ranges(cx),
 4175            vec![
 4176                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4177                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4178            ]
 4179        );
 4180    });
 4181}
 4182
 4183#[gpui::test]
 4184fn test_move_line_up_down(cx: &mut TestAppContext) {
 4185    init_test(cx, |_| {});
 4186
 4187    let editor = cx.add_window(|window, cx| {
 4188        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4189        build_editor(buffer, window, cx)
 4190    });
 4191    _ = editor.update(cx, |editor, window, cx| {
 4192        editor.fold_creases(
 4193            vec![
 4194                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4195                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4196                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4197            ],
 4198            true,
 4199            window,
 4200            cx,
 4201        );
 4202        editor.change_selections(None, window, cx, |s| {
 4203            s.select_display_ranges([
 4204                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4205                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4206                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4207                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4208            ])
 4209        });
 4210        assert_eq!(
 4211            editor.display_text(cx),
 4212            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4213        );
 4214
 4215        editor.move_line_up(&MoveLineUp, window, cx);
 4216        assert_eq!(
 4217            editor.display_text(cx),
 4218            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4219        );
 4220        assert_eq!(
 4221            editor.selections.display_ranges(cx),
 4222            vec![
 4223                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4224                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4225                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4226                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4227            ]
 4228        );
 4229    });
 4230
 4231    _ = editor.update(cx, |editor, window, cx| {
 4232        editor.move_line_down(&MoveLineDown, window, cx);
 4233        assert_eq!(
 4234            editor.display_text(cx),
 4235            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4236        );
 4237        assert_eq!(
 4238            editor.selections.display_ranges(cx),
 4239            vec![
 4240                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4241                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4242                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4243                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4244            ]
 4245        );
 4246    });
 4247
 4248    _ = editor.update(cx, |editor, window, cx| {
 4249        editor.move_line_down(&MoveLineDown, window, cx);
 4250        assert_eq!(
 4251            editor.display_text(cx),
 4252            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4253        );
 4254        assert_eq!(
 4255            editor.selections.display_ranges(cx),
 4256            vec![
 4257                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4258                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4259                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4260                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4261            ]
 4262        );
 4263    });
 4264
 4265    _ = editor.update(cx, |editor, window, cx| {
 4266        editor.move_line_up(&MoveLineUp, window, cx);
 4267        assert_eq!(
 4268            editor.display_text(cx),
 4269            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4270        );
 4271        assert_eq!(
 4272            editor.selections.display_ranges(cx),
 4273            vec![
 4274                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4275                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4276                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4277                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4278            ]
 4279        );
 4280    });
 4281}
 4282
 4283#[gpui::test]
 4284fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4285    init_test(cx, |_| {});
 4286
 4287    let editor = cx.add_window(|window, cx| {
 4288        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4289        build_editor(buffer, window, cx)
 4290    });
 4291    _ = editor.update(cx, |editor, window, cx| {
 4292        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4293        editor.insert_blocks(
 4294            [BlockProperties {
 4295                style: BlockStyle::Fixed,
 4296                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4297                height: Some(1),
 4298                render: Arc::new(|_| div().into_any()),
 4299                priority: 0,
 4300            }],
 4301            Some(Autoscroll::fit()),
 4302            cx,
 4303        );
 4304        editor.change_selections(None, window, cx, |s| {
 4305            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4306        });
 4307        editor.move_line_down(&MoveLineDown, window, cx);
 4308    });
 4309}
 4310
 4311#[gpui::test]
 4312async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4313    init_test(cx, |_| {});
 4314
 4315    let mut cx = EditorTestContext::new(cx).await;
 4316    cx.set_state(
 4317        &"
 4318            ˇzero
 4319            one
 4320            two
 4321            three
 4322            four
 4323            five
 4324        "
 4325        .unindent(),
 4326    );
 4327
 4328    // Create a four-line block that replaces three lines of text.
 4329    cx.update_editor(|editor, window, cx| {
 4330        let snapshot = editor.snapshot(window, cx);
 4331        let snapshot = &snapshot.buffer_snapshot;
 4332        let placement = BlockPlacement::Replace(
 4333            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4334        );
 4335        editor.insert_blocks(
 4336            [BlockProperties {
 4337                placement,
 4338                height: Some(4),
 4339                style: BlockStyle::Sticky,
 4340                render: Arc::new(|_| gpui::div().into_any_element()),
 4341                priority: 0,
 4342            }],
 4343            None,
 4344            cx,
 4345        );
 4346    });
 4347
 4348    // Move down so that the cursor touches the block.
 4349    cx.update_editor(|editor, window, cx| {
 4350        editor.move_down(&Default::default(), window, cx);
 4351    });
 4352    cx.assert_editor_state(
 4353        &"
 4354            zero
 4355            «one
 4356            two
 4357            threeˇ»
 4358            four
 4359            five
 4360        "
 4361        .unindent(),
 4362    );
 4363
 4364    // Move down past the block.
 4365    cx.update_editor(|editor, window, cx| {
 4366        editor.move_down(&Default::default(), window, cx);
 4367    });
 4368    cx.assert_editor_state(
 4369        &"
 4370            zero
 4371            one
 4372            two
 4373            three
 4374            ˇfour
 4375            five
 4376        "
 4377        .unindent(),
 4378    );
 4379}
 4380
 4381#[gpui::test]
 4382fn test_transpose(cx: &mut TestAppContext) {
 4383    init_test(cx, |_| {});
 4384
 4385    _ = cx.add_window(|window, cx| {
 4386        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4387        editor.set_style(EditorStyle::default(), window, cx);
 4388        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4389        editor.transpose(&Default::default(), window, cx);
 4390        assert_eq!(editor.text(cx), "bac");
 4391        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4392
 4393        editor.transpose(&Default::default(), window, cx);
 4394        assert_eq!(editor.text(cx), "bca");
 4395        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4396
 4397        editor.transpose(&Default::default(), window, cx);
 4398        assert_eq!(editor.text(cx), "bac");
 4399        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4400
 4401        editor
 4402    });
 4403
 4404    _ = cx.add_window(|window, cx| {
 4405        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4406        editor.set_style(EditorStyle::default(), window, cx);
 4407        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4408        editor.transpose(&Default::default(), window, cx);
 4409        assert_eq!(editor.text(cx), "acb\nde");
 4410        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4411
 4412        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4413        editor.transpose(&Default::default(), window, cx);
 4414        assert_eq!(editor.text(cx), "acbd\ne");
 4415        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4416
 4417        editor.transpose(&Default::default(), window, cx);
 4418        assert_eq!(editor.text(cx), "acbde\n");
 4419        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4420
 4421        editor.transpose(&Default::default(), window, cx);
 4422        assert_eq!(editor.text(cx), "acbd\ne");
 4423        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4424
 4425        editor
 4426    });
 4427
 4428    _ = cx.add_window(|window, cx| {
 4429        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4430        editor.set_style(EditorStyle::default(), window, cx);
 4431        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4432        editor.transpose(&Default::default(), window, cx);
 4433        assert_eq!(editor.text(cx), "bacd\ne");
 4434        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4435
 4436        editor.transpose(&Default::default(), window, cx);
 4437        assert_eq!(editor.text(cx), "bcade\n");
 4438        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4439
 4440        editor.transpose(&Default::default(), window, cx);
 4441        assert_eq!(editor.text(cx), "bcda\ne");
 4442        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4443
 4444        editor.transpose(&Default::default(), window, cx);
 4445        assert_eq!(editor.text(cx), "bcade\n");
 4446        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4447
 4448        editor.transpose(&Default::default(), window, cx);
 4449        assert_eq!(editor.text(cx), "bcaed\n");
 4450        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4451
 4452        editor
 4453    });
 4454
 4455    _ = cx.add_window(|window, cx| {
 4456        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4457        editor.set_style(EditorStyle::default(), window, cx);
 4458        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4459        editor.transpose(&Default::default(), window, cx);
 4460        assert_eq!(editor.text(cx), "🏀🍐✋");
 4461        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4462
 4463        editor.transpose(&Default::default(), window, cx);
 4464        assert_eq!(editor.text(cx), "🏀✋🍐");
 4465        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4466
 4467        editor.transpose(&Default::default(), window, cx);
 4468        assert_eq!(editor.text(cx), "🏀🍐✋");
 4469        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4470
 4471        editor
 4472    });
 4473}
 4474
 4475#[gpui::test]
 4476async fn test_rewrap(cx: &mut TestAppContext) {
 4477    init_test(cx, |settings| {
 4478        settings.languages.extend([
 4479            (
 4480                "Markdown".into(),
 4481                LanguageSettingsContent {
 4482                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4483                    ..Default::default()
 4484                },
 4485            ),
 4486            (
 4487                "Plain Text".into(),
 4488                LanguageSettingsContent {
 4489                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4490                    ..Default::default()
 4491                },
 4492            ),
 4493        ])
 4494    });
 4495
 4496    let mut cx = EditorTestContext::new(cx).await;
 4497
 4498    let language_with_c_comments = Arc::new(Language::new(
 4499        LanguageConfig {
 4500            line_comments: vec!["// ".into()],
 4501            ..LanguageConfig::default()
 4502        },
 4503        None,
 4504    ));
 4505    let language_with_pound_comments = Arc::new(Language::new(
 4506        LanguageConfig {
 4507            line_comments: vec!["# ".into()],
 4508            ..LanguageConfig::default()
 4509        },
 4510        None,
 4511    ));
 4512    let markdown_language = Arc::new(Language::new(
 4513        LanguageConfig {
 4514            name: "Markdown".into(),
 4515            ..LanguageConfig::default()
 4516        },
 4517        None,
 4518    ));
 4519    let language_with_doc_comments = Arc::new(Language::new(
 4520        LanguageConfig {
 4521            line_comments: vec!["// ".into(), "/// ".into()],
 4522            ..LanguageConfig::default()
 4523        },
 4524        Some(tree_sitter_rust::LANGUAGE.into()),
 4525    ));
 4526
 4527    let plaintext_language = Arc::new(Language::new(
 4528        LanguageConfig {
 4529            name: "Plain Text".into(),
 4530            ..LanguageConfig::default()
 4531        },
 4532        None,
 4533    ));
 4534
 4535    assert_rewrap(
 4536        indoc! {"
 4537            // ˇ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.
 4538        "},
 4539        indoc! {"
 4540            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4541            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4542            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4543            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4544            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4545            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4546            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4547            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4548            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4549            // porttitor id. Aliquam id accumsan eros.
 4550        "},
 4551        language_with_c_comments.clone(),
 4552        &mut cx,
 4553    );
 4554
 4555    // Test that rewrapping works inside of a selection
 4556    assert_rewrap(
 4557        indoc! {"
 4558            «// 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.ˇ»
 4559        "},
 4560        indoc! {"
 4561            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4562            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4563            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4564            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4565            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4566            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4567            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4568            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4569            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4570            // porttitor id. Aliquam id accumsan eros.ˇ»
 4571        "},
 4572        language_with_c_comments.clone(),
 4573        &mut cx,
 4574    );
 4575
 4576    // Test that cursors that expand to the same region are collapsed.
 4577    assert_rewrap(
 4578        indoc! {"
 4579            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4580            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4581            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4582            // ˇ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.
 4583        "},
 4584        indoc! {"
 4585            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4586            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4587            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4588            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4589            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4590            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4591            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4592            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4593            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4594            // porttitor id. Aliquam id accumsan eros.
 4595        "},
 4596        language_with_c_comments.clone(),
 4597        &mut cx,
 4598    );
 4599
 4600    // Test that non-contiguous selections are treated separately.
 4601    assert_rewrap(
 4602        indoc! {"
 4603            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4604            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4605            //
 4606            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4607            // ˇ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.
 4608        "},
 4609        indoc! {"
 4610            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4611            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4612            // auctor, eu lacinia sapien scelerisque.
 4613            //
 4614            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 4615            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4616            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 4617            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 4618            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 4619            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 4620            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 4621        "},
 4622        language_with_c_comments.clone(),
 4623        &mut cx,
 4624    );
 4625
 4626    // Test that different comment prefixes are supported.
 4627    assert_rewrap(
 4628        indoc! {"
 4629            # ˇ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.
 4630        "},
 4631        indoc! {"
 4632            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4633            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4634            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4635            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4636            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 4637            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 4638            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 4639            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 4640            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 4641            # accumsan eros.
 4642        "},
 4643        language_with_pound_comments.clone(),
 4644        &mut cx,
 4645    );
 4646
 4647    // Test that rewrapping is ignored outside of comments in most languages.
 4648    assert_rewrap(
 4649        indoc! {"
 4650            /// Adds two numbers.
 4651            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4652            fn add(a: u32, b: u32) -> u32 {
 4653                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ˇ
 4654            }
 4655        "},
 4656        indoc! {"
 4657            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 4658            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4659            fn add(a: u32, b: u32) -> u32 {
 4660                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ˇ
 4661            }
 4662        "},
 4663        language_with_doc_comments.clone(),
 4664        &mut cx,
 4665    );
 4666
 4667    // Test that rewrapping works in Markdown and Plain Text languages.
 4668    assert_rewrap(
 4669        indoc! {"
 4670            # Hello
 4671
 4672            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.
 4673        "},
 4674        indoc! {"
 4675            # Hello
 4676
 4677            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4678            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4679            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4680            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4681            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4682            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4683            Integer sit amet scelerisque nisi.
 4684        "},
 4685        markdown_language,
 4686        &mut cx,
 4687    );
 4688
 4689    assert_rewrap(
 4690        indoc! {"
 4691            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.
 4692        "},
 4693        indoc! {"
 4694            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4695            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4696            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4697            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4698            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4699            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4700            Integer sit amet scelerisque nisi.
 4701        "},
 4702        plaintext_language,
 4703        &mut cx,
 4704    );
 4705
 4706    // Test rewrapping unaligned comments in a selection.
 4707    assert_rewrap(
 4708        indoc! {"
 4709            fn foo() {
 4710                if true {
 4711            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4712            // Praesent semper egestas tellus id dignissim.ˇ»
 4713                    do_something();
 4714                } else {
 4715                    //
 4716                }
 4717            }
 4718        "},
 4719        indoc! {"
 4720            fn foo() {
 4721                if true {
 4722            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4723                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4724                    // egestas tellus id dignissim.ˇ»
 4725                    do_something();
 4726                } else {
 4727                    //
 4728                }
 4729            }
 4730        "},
 4731        language_with_doc_comments.clone(),
 4732        &mut cx,
 4733    );
 4734
 4735    assert_rewrap(
 4736        indoc! {"
 4737            fn foo() {
 4738                if true {
 4739            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4740            // Praesent semper egestas tellus id dignissim.»
 4741                    do_something();
 4742                } else {
 4743                    //
 4744                }
 4745
 4746            }
 4747        "},
 4748        indoc! {"
 4749            fn foo() {
 4750                if true {
 4751            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4752                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4753                    // egestas tellus id dignissim.»
 4754                    do_something();
 4755                } else {
 4756                    //
 4757                }
 4758
 4759            }
 4760        "},
 4761        language_with_doc_comments.clone(),
 4762        &mut cx,
 4763    );
 4764
 4765    #[track_caller]
 4766    fn assert_rewrap(
 4767        unwrapped_text: &str,
 4768        wrapped_text: &str,
 4769        language: Arc<Language>,
 4770        cx: &mut EditorTestContext,
 4771    ) {
 4772        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4773        cx.set_state(unwrapped_text);
 4774        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 4775        cx.assert_editor_state(wrapped_text);
 4776    }
 4777}
 4778
 4779#[gpui::test]
 4780async fn test_hard_wrap(cx: &mut TestAppContext) {
 4781    init_test(cx, |_| {});
 4782    let mut cx = EditorTestContext::new(cx).await;
 4783
 4784    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 4785    cx.update_editor(|editor, _, cx| {
 4786        editor.set_hard_wrap(Some(14), cx);
 4787    });
 4788
 4789    cx.set_state(indoc!(
 4790        "
 4791        one two three ˇ
 4792        "
 4793    ));
 4794    cx.simulate_input("four");
 4795    cx.run_until_parked();
 4796
 4797    cx.assert_editor_state(indoc!(
 4798        "
 4799        one two three
 4800        fourˇ
 4801        "
 4802    ));
 4803
 4804    cx.update_editor(|editor, window, cx| {
 4805        editor.newline(&Default::default(), window, cx);
 4806    });
 4807    cx.run_until_parked();
 4808    cx.assert_editor_state(indoc!(
 4809        "
 4810        one two three
 4811        four
 4812        ˇ
 4813        "
 4814    ));
 4815
 4816    cx.simulate_input("five");
 4817    cx.run_until_parked();
 4818    cx.assert_editor_state(indoc!(
 4819        "
 4820        one two three
 4821        four
 4822        fiveˇ
 4823        "
 4824    ));
 4825
 4826    cx.update_editor(|editor, window, cx| {
 4827        editor.newline(&Default::default(), window, cx);
 4828    });
 4829    cx.run_until_parked();
 4830    cx.simulate_input("# ");
 4831    cx.run_until_parked();
 4832    cx.assert_editor_state(indoc!(
 4833        "
 4834        one two three
 4835        four
 4836        five
 4837        # ˇ
 4838        "
 4839    ));
 4840
 4841    cx.update_editor(|editor, window, cx| {
 4842        editor.newline(&Default::default(), window, cx);
 4843    });
 4844    cx.run_until_parked();
 4845    cx.assert_editor_state(indoc!(
 4846        "
 4847        one two three
 4848        four
 4849        five
 4850        #\x20
 4851 4852        "
 4853    ));
 4854
 4855    cx.simulate_input(" 6");
 4856    cx.run_until_parked();
 4857    cx.assert_editor_state(indoc!(
 4858        "
 4859        one two three
 4860        four
 4861        five
 4862        #
 4863        # 6ˇ
 4864        "
 4865    ));
 4866}
 4867
 4868#[gpui::test]
 4869async fn test_clipboard(cx: &mut TestAppContext) {
 4870    init_test(cx, |_| {});
 4871
 4872    let mut cx = EditorTestContext::new(cx).await;
 4873
 4874    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 4875    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4876    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 4877
 4878    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 4879    cx.set_state("two ˇfour ˇsix ˇ");
 4880    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4881    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 4882
 4883    // Paste again but with only two cursors. Since the number of cursors doesn't
 4884    // match the number of slices in the clipboard, the entire clipboard text
 4885    // is pasted at each cursor.
 4886    cx.set_state("ˇtwo one✅ four three six five ˇ");
 4887    cx.update_editor(|e, window, cx| {
 4888        e.handle_input("( ", window, cx);
 4889        e.paste(&Paste, window, cx);
 4890        e.handle_input(") ", window, cx);
 4891    });
 4892    cx.assert_editor_state(
 4893        &([
 4894            "( one✅ ",
 4895            "three ",
 4896            "five ) ˇtwo one✅ four three six five ( one✅ ",
 4897            "three ",
 4898            "five ) ˇ",
 4899        ]
 4900        .join("\n")),
 4901    );
 4902
 4903    // Cut with three selections, one of which is full-line.
 4904    cx.set_state(indoc! {"
 4905        1«2ˇ»3
 4906        4ˇ567
 4907        «8ˇ»9"});
 4908    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4909    cx.assert_editor_state(indoc! {"
 4910        1ˇ3
 4911        ˇ9"});
 4912
 4913    // Paste with three selections, noticing how the copied selection that was full-line
 4914    // gets inserted before the second cursor.
 4915    cx.set_state(indoc! {"
 4916        1ˇ3
 4917 4918        «oˇ»ne"});
 4919    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4920    cx.assert_editor_state(indoc! {"
 4921        12ˇ3
 4922        4567
 4923 4924        8ˇne"});
 4925
 4926    // Copy with a single cursor only, which writes the whole line into the clipboard.
 4927    cx.set_state(indoc! {"
 4928        The quick brown
 4929        fox juˇmps over
 4930        the lazy dog"});
 4931    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4932    assert_eq!(
 4933        cx.read_from_clipboard()
 4934            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4935        Some("fox jumps over\n".to_string())
 4936    );
 4937
 4938    // Paste with three selections, noticing how the copied full-line selection is inserted
 4939    // before the empty selections but replaces the selection that is non-empty.
 4940    cx.set_state(indoc! {"
 4941        Tˇhe quick brown
 4942        «foˇ»x jumps over
 4943        tˇhe lazy dog"});
 4944    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4945    cx.assert_editor_state(indoc! {"
 4946        fox jumps over
 4947        Tˇhe quick brown
 4948        fox jumps over
 4949        ˇx jumps over
 4950        fox jumps over
 4951        tˇhe lazy dog"});
 4952}
 4953
 4954#[gpui::test]
 4955async fn test_copy_trim(cx: &mut TestAppContext) {
 4956    init_test(cx, |_| {});
 4957
 4958    let mut cx = EditorTestContext::new(cx).await;
 4959    cx.set_state(
 4960        r#"            «for selection in selections.iter() {
 4961            let mut start = selection.start;
 4962            let mut end = selection.end;
 4963            let is_entire_line = selection.is_empty();
 4964            if is_entire_line {
 4965                start = Point::new(start.row, 0);ˇ»
 4966                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 4967            }
 4968        "#,
 4969    );
 4970    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4971    assert_eq!(
 4972        cx.read_from_clipboard()
 4973            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4974        Some(
 4975            "for selection in selections.iter() {
 4976            let mut start = selection.start;
 4977            let mut end = selection.end;
 4978            let is_entire_line = selection.is_empty();
 4979            if is_entire_line {
 4980                start = Point::new(start.row, 0);"
 4981                .to_string()
 4982        ),
 4983        "Regular copying preserves all indentation selected",
 4984    );
 4985    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 4986    assert_eq!(
 4987        cx.read_from_clipboard()
 4988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4989        Some(
 4990            "for selection in selections.iter() {
 4991let mut start = selection.start;
 4992let mut end = selection.end;
 4993let is_entire_line = selection.is_empty();
 4994if is_entire_line {
 4995    start = Point::new(start.row, 0);"
 4996                .to_string()
 4997        ),
 4998        "Copying with stripping should strip all leading whitespaces"
 4999    );
 5000
 5001    cx.set_state(
 5002        r#"       «     for selection in selections.iter() {
 5003            let mut start = selection.start;
 5004            let mut end = selection.end;
 5005            let is_entire_line = selection.is_empty();
 5006            if is_entire_line {
 5007                start = Point::new(start.row, 0);ˇ»
 5008                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5009            }
 5010        "#,
 5011    );
 5012    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5013    assert_eq!(
 5014        cx.read_from_clipboard()
 5015            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5016        Some(
 5017            "     for selection in selections.iter() {
 5018            let mut start = selection.start;
 5019            let mut end = selection.end;
 5020            let is_entire_line = selection.is_empty();
 5021            if is_entire_line {
 5022                start = Point::new(start.row, 0);"
 5023                .to_string()
 5024        ),
 5025        "Regular copying preserves all indentation selected",
 5026    );
 5027    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5028    assert_eq!(
 5029        cx.read_from_clipboard()
 5030            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5031        Some(
 5032            "for selection in selections.iter() {
 5033let mut start = selection.start;
 5034let mut end = selection.end;
 5035let is_entire_line = selection.is_empty();
 5036if is_entire_line {
 5037    start = Point::new(start.row, 0);"
 5038                .to_string()
 5039        ),
 5040        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5041    );
 5042
 5043    cx.set_state(
 5044        r#"       «ˇ     for selection in selections.iter() {
 5045            let mut start = selection.start;
 5046            let mut end = selection.end;
 5047            let is_entire_line = selection.is_empty();
 5048            if is_entire_line {
 5049                start = Point::new(start.row, 0);»
 5050                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5051            }
 5052        "#,
 5053    );
 5054    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5055    assert_eq!(
 5056        cx.read_from_clipboard()
 5057            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5058        Some(
 5059            "     for selection in selections.iter() {
 5060            let mut start = selection.start;
 5061            let mut end = selection.end;
 5062            let is_entire_line = selection.is_empty();
 5063            if is_entire_line {
 5064                start = Point::new(start.row, 0);"
 5065                .to_string()
 5066        ),
 5067        "Regular copying for reverse selection works the same",
 5068    );
 5069    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5070    assert_eq!(
 5071        cx.read_from_clipboard()
 5072            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5073        Some(
 5074            "for selection in selections.iter() {
 5075let mut start = selection.start;
 5076let mut end = selection.end;
 5077let is_entire_line = selection.is_empty();
 5078if is_entire_line {
 5079    start = Point::new(start.row, 0);"
 5080                .to_string()
 5081        ),
 5082        "Copying with stripping for reverse selection works the same"
 5083    );
 5084
 5085    cx.set_state(
 5086        r#"            for selection «in selections.iter() {
 5087            let mut start = selection.start;
 5088            let mut end = selection.end;
 5089            let is_entire_line = selection.is_empty();
 5090            if is_entire_line {
 5091                start = Point::new(start.row, 0);ˇ»
 5092                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5093            }
 5094        "#,
 5095    );
 5096    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5097    assert_eq!(
 5098        cx.read_from_clipboard()
 5099            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5100        Some(
 5101            "in selections.iter() {
 5102            let mut start = selection.start;
 5103            let mut end = selection.end;
 5104            let is_entire_line = selection.is_empty();
 5105            if is_entire_line {
 5106                start = Point::new(start.row, 0);"
 5107                .to_string()
 5108        ),
 5109        "When selecting past the indent, the copying works as usual",
 5110    );
 5111    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5112    assert_eq!(
 5113        cx.read_from_clipboard()
 5114            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5115        Some(
 5116            "in selections.iter() {
 5117            let mut start = selection.start;
 5118            let mut end = selection.end;
 5119            let is_entire_line = selection.is_empty();
 5120            if is_entire_line {
 5121                start = Point::new(start.row, 0);"
 5122                .to_string()
 5123        ),
 5124        "When selecting past the indent, nothing is trimmed"
 5125    );
 5126}
 5127
 5128#[gpui::test]
 5129async fn test_paste_multiline(cx: &mut TestAppContext) {
 5130    init_test(cx, |_| {});
 5131
 5132    let mut cx = EditorTestContext::new(cx).await;
 5133    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5134
 5135    // Cut an indented block, without the leading whitespace.
 5136    cx.set_state(indoc! {"
 5137        const a: B = (
 5138            c(),
 5139            «d(
 5140                e,
 5141                f
 5142            )ˇ»
 5143        );
 5144    "});
 5145    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5146    cx.assert_editor_state(indoc! {"
 5147        const a: B = (
 5148            c(),
 5149            ˇ
 5150        );
 5151    "});
 5152
 5153    // Paste it at the same position.
 5154    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5155    cx.assert_editor_state(indoc! {"
 5156        const a: B = (
 5157            c(),
 5158            d(
 5159                e,
 5160                f
 5161 5162        );
 5163    "});
 5164
 5165    // Paste it at a line with a lower indent level.
 5166    cx.set_state(indoc! {"
 5167        ˇ
 5168        const a: B = (
 5169            c(),
 5170        );
 5171    "});
 5172    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5173    cx.assert_editor_state(indoc! {"
 5174        d(
 5175            e,
 5176            f
 5177 5178        const a: B = (
 5179            c(),
 5180        );
 5181    "});
 5182
 5183    // Cut an indented block, with the leading whitespace.
 5184    cx.set_state(indoc! {"
 5185        const a: B = (
 5186            c(),
 5187        «    d(
 5188                e,
 5189                f
 5190            )
 5191        ˇ»);
 5192    "});
 5193    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5194    cx.assert_editor_state(indoc! {"
 5195        const a: B = (
 5196            c(),
 5197        ˇ);
 5198    "});
 5199
 5200    // Paste it at the same position.
 5201    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5202    cx.assert_editor_state(indoc! {"
 5203        const a: B = (
 5204            c(),
 5205            d(
 5206                e,
 5207                f
 5208            )
 5209        ˇ);
 5210    "});
 5211
 5212    // Paste it at a line with a higher indent level.
 5213    cx.set_state(indoc! {"
 5214        const a: B = (
 5215            c(),
 5216            d(
 5217                e,
 5218 5219            )
 5220        );
 5221    "});
 5222    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5223    cx.assert_editor_state(indoc! {"
 5224        const a: B = (
 5225            c(),
 5226            d(
 5227                e,
 5228                f    d(
 5229                    e,
 5230                    f
 5231                )
 5232        ˇ
 5233            )
 5234        );
 5235    "});
 5236
 5237    // Copy an indented block, starting mid-line
 5238    cx.set_state(indoc! {"
 5239        const a: B = (
 5240            c(),
 5241            somethin«g(
 5242                e,
 5243                f
 5244            )ˇ»
 5245        );
 5246    "});
 5247    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5248
 5249    // Paste it on a line with a lower indent level
 5250    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5251    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5252    cx.assert_editor_state(indoc! {"
 5253        const a: B = (
 5254            c(),
 5255            something(
 5256                e,
 5257                f
 5258            )
 5259        );
 5260        g(
 5261            e,
 5262            f
 5263"});
 5264}
 5265
 5266#[gpui::test]
 5267async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5268    init_test(cx, |_| {});
 5269
 5270    cx.write_to_clipboard(ClipboardItem::new_string(
 5271        "    d(\n        e\n    );\n".into(),
 5272    ));
 5273
 5274    let mut cx = EditorTestContext::new(cx).await;
 5275    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5276
 5277    cx.set_state(indoc! {"
 5278        fn a() {
 5279            b();
 5280            if c() {
 5281                ˇ
 5282            }
 5283        }
 5284    "});
 5285
 5286    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5287    cx.assert_editor_state(indoc! {"
 5288        fn a() {
 5289            b();
 5290            if c() {
 5291                d(
 5292                    e
 5293                );
 5294        ˇ
 5295            }
 5296        }
 5297    "});
 5298
 5299    cx.set_state(indoc! {"
 5300        fn a() {
 5301            b();
 5302            ˇ
 5303        }
 5304    "});
 5305
 5306    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5307    cx.assert_editor_state(indoc! {"
 5308        fn a() {
 5309            b();
 5310            d(
 5311                e
 5312            );
 5313        ˇ
 5314        }
 5315    "});
 5316}
 5317
 5318#[gpui::test]
 5319fn test_select_all(cx: &mut TestAppContext) {
 5320    init_test(cx, |_| {});
 5321
 5322    let editor = cx.add_window(|window, cx| {
 5323        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5324        build_editor(buffer, window, cx)
 5325    });
 5326    _ = editor.update(cx, |editor, window, cx| {
 5327        editor.select_all(&SelectAll, window, cx);
 5328        assert_eq!(
 5329            editor.selections.display_ranges(cx),
 5330            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5331        );
 5332    });
 5333}
 5334
 5335#[gpui::test]
 5336fn test_select_line(cx: &mut TestAppContext) {
 5337    init_test(cx, |_| {});
 5338
 5339    let editor = cx.add_window(|window, cx| {
 5340        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5341        build_editor(buffer, window, cx)
 5342    });
 5343    _ = editor.update(cx, |editor, window, cx| {
 5344        editor.change_selections(None, window, cx, |s| {
 5345            s.select_display_ranges([
 5346                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5347                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5348                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5349                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5350            ])
 5351        });
 5352        editor.select_line(&SelectLine, window, cx);
 5353        assert_eq!(
 5354            editor.selections.display_ranges(cx),
 5355            vec![
 5356                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5357                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5358            ]
 5359        );
 5360    });
 5361
 5362    _ = editor.update(cx, |editor, window, cx| {
 5363        editor.select_line(&SelectLine, window, cx);
 5364        assert_eq!(
 5365            editor.selections.display_ranges(cx),
 5366            vec![
 5367                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5368                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5369            ]
 5370        );
 5371    });
 5372
 5373    _ = editor.update(cx, |editor, window, cx| {
 5374        editor.select_line(&SelectLine, window, cx);
 5375        assert_eq!(
 5376            editor.selections.display_ranges(cx),
 5377            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5378        );
 5379    });
 5380}
 5381
 5382#[gpui::test]
 5383async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5384    init_test(cx, |_| {});
 5385    let mut cx = EditorTestContext::new(cx).await;
 5386
 5387    #[track_caller]
 5388    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5389        cx.set_state(initial_state);
 5390        cx.update_editor(|e, window, cx| {
 5391            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5392        });
 5393        cx.assert_editor_state(expected_state);
 5394    }
 5395
 5396    // Selection starts and ends at the middle of lines, left-to-right
 5397    test(
 5398        &mut cx,
 5399        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5400        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5401    );
 5402    // Same thing, right-to-left
 5403    test(
 5404        &mut cx,
 5405        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5406        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5407    );
 5408
 5409    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5410    test(
 5411        &mut cx,
 5412        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5413        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5414    );
 5415    // Same thing, right-to-left
 5416    test(
 5417        &mut cx,
 5418        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5419        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5420    );
 5421
 5422    // Whole buffer, left-to-right, last line ends with newline
 5423    test(
 5424        &mut cx,
 5425        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5426        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5427    );
 5428    // Same thing, right-to-left
 5429    test(
 5430        &mut cx,
 5431        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5432        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5433    );
 5434
 5435    // Starts at the end of a line, ends at the start of another
 5436    test(
 5437        &mut cx,
 5438        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5439        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5440    );
 5441}
 5442
 5443#[gpui::test]
 5444async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5445    init_test(cx, |_| {});
 5446
 5447    let editor = cx.add_window(|window, cx| {
 5448        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5449        build_editor(buffer, window, cx)
 5450    });
 5451
 5452    // setup
 5453    _ = editor.update(cx, |editor, window, cx| {
 5454        editor.fold_creases(
 5455            vec![
 5456                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5457                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5458                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5459            ],
 5460            true,
 5461            window,
 5462            cx,
 5463        );
 5464        assert_eq!(
 5465            editor.display_text(cx),
 5466            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5467        );
 5468    });
 5469
 5470    _ = editor.update(cx, |editor, window, cx| {
 5471        editor.change_selections(None, window, cx, |s| {
 5472            s.select_display_ranges([
 5473                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5474                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5475                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5476                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5477            ])
 5478        });
 5479        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5480        assert_eq!(
 5481            editor.display_text(cx),
 5482            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5483        );
 5484    });
 5485    EditorTestContext::for_editor(editor, cx)
 5486        .await
 5487        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5488
 5489    _ = editor.update(cx, |editor, window, cx| {
 5490        editor.change_selections(None, window, cx, |s| {
 5491            s.select_display_ranges([
 5492                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5493            ])
 5494        });
 5495        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5496        assert_eq!(
 5497            editor.display_text(cx),
 5498            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5499        );
 5500        assert_eq!(
 5501            editor.selections.display_ranges(cx),
 5502            [
 5503                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5504                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5505                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5506                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5507                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5508                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5509                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5510            ]
 5511        );
 5512    });
 5513    EditorTestContext::for_editor(editor, cx)
 5514        .await
 5515        .assert_editor_state(
 5516            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5517        );
 5518}
 5519
 5520#[gpui::test]
 5521async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5522    init_test(cx, |_| {});
 5523
 5524    let mut cx = EditorTestContext::new(cx).await;
 5525
 5526    cx.set_state(indoc!(
 5527        r#"abc
 5528           defˇghi
 5529
 5530           jk
 5531           nlmo
 5532           "#
 5533    ));
 5534
 5535    cx.update_editor(|editor, window, cx| {
 5536        editor.add_selection_above(&Default::default(), window, cx);
 5537    });
 5538
 5539    cx.assert_editor_state(indoc!(
 5540        r#"abcˇ
 5541           defˇghi
 5542
 5543           jk
 5544           nlmo
 5545           "#
 5546    ));
 5547
 5548    cx.update_editor(|editor, window, cx| {
 5549        editor.add_selection_above(&Default::default(), window, cx);
 5550    });
 5551
 5552    cx.assert_editor_state(indoc!(
 5553        r#"abcˇ
 5554            defˇghi
 5555
 5556            jk
 5557            nlmo
 5558            "#
 5559    ));
 5560
 5561    cx.update_editor(|editor, window, cx| {
 5562        editor.add_selection_below(&Default::default(), window, cx);
 5563    });
 5564
 5565    cx.assert_editor_state(indoc!(
 5566        r#"abc
 5567           defˇghi
 5568
 5569           jk
 5570           nlmo
 5571           "#
 5572    ));
 5573
 5574    cx.update_editor(|editor, window, cx| {
 5575        editor.undo_selection(&Default::default(), window, cx);
 5576    });
 5577
 5578    cx.assert_editor_state(indoc!(
 5579        r#"abcˇ
 5580           defˇghi
 5581
 5582           jk
 5583           nlmo
 5584           "#
 5585    ));
 5586
 5587    cx.update_editor(|editor, window, cx| {
 5588        editor.redo_selection(&Default::default(), window, cx);
 5589    });
 5590
 5591    cx.assert_editor_state(indoc!(
 5592        r#"abc
 5593           defˇghi
 5594
 5595           jk
 5596           nlmo
 5597           "#
 5598    ));
 5599
 5600    cx.update_editor(|editor, window, cx| {
 5601        editor.add_selection_below(&Default::default(), window, cx);
 5602    });
 5603
 5604    cx.assert_editor_state(indoc!(
 5605        r#"abc
 5606           defˇghi
 5607
 5608           jk
 5609           nlmˇo
 5610           "#
 5611    ));
 5612
 5613    cx.update_editor(|editor, window, cx| {
 5614        editor.add_selection_below(&Default::default(), window, cx);
 5615    });
 5616
 5617    cx.assert_editor_state(indoc!(
 5618        r#"abc
 5619           defˇghi
 5620
 5621           jk
 5622           nlmˇo
 5623           "#
 5624    ));
 5625
 5626    // change selections
 5627    cx.set_state(indoc!(
 5628        r#"abc
 5629           def«ˇg»hi
 5630
 5631           jk
 5632           nlmo
 5633           "#
 5634    ));
 5635
 5636    cx.update_editor(|editor, window, cx| {
 5637        editor.add_selection_below(&Default::default(), window, cx);
 5638    });
 5639
 5640    cx.assert_editor_state(indoc!(
 5641        r#"abc
 5642           def«ˇg»hi
 5643
 5644           jk
 5645           nlm«ˇo»
 5646           "#
 5647    ));
 5648
 5649    cx.update_editor(|editor, window, cx| {
 5650        editor.add_selection_below(&Default::default(), window, cx);
 5651    });
 5652
 5653    cx.assert_editor_state(indoc!(
 5654        r#"abc
 5655           def«ˇg»hi
 5656
 5657           jk
 5658           nlm«ˇo»
 5659           "#
 5660    ));
 5661
 5662    cx.update_editor(|editor, window, cx| {
 5663        editor.add_selection_above(&Default::default(), window, cx);
 5664    });
 5665
 5666    cx.assert_editor_state(indoc!(
 5667        r#"abc
 5668           def«ˇg»hi
 5669
 5670           jk
 5671           nlmo
 5672           "#
 5673    ));
 5674
 5675    cx.update_editor(|editor, window, cx| {
 5676        editor.add_selection_above(&Default::default(), window, cx);
 5677    });
 5678
 5679    cx.assert_editor_state(indoc!(
 5680        r#"abc
 5681           def«ˇg»hi
 5682
 5683           jk
 5684           nlmo
 5685           "#
 5686    ));
 5687
 5688    // Change selections again
 5689    cx.set_state(indoc!(
 5690        r#"a«bc
 5691           defgˇ»hi
 5692
 5693           jk
 5694           nlmo
 5695           "#
 5696    ));
 5697
 5698    cx.update_editor(|editor, window, cx| {
 5699        editor.add_selection_below(&Default::default(), window, cx);
 5700    });
 5701
 5702    cx.assert_editor_state(indoc!(
 5703        r#"a«bcˇ»
 5704           d«efgˇ»hi
 5705
 5706           j«kˇ»
 5707           nlmo
 5708           "#
 5709    ));
 5710
 5711    cx.update_editor(|editor, window, cx| {
 5712        editor.add_selection_below(&Default::default(), window, cx);
 5713    });
 5714    cx.assert_editor_state(indoc!(
 5715        r#"a«bcˇ»
 5716           d«efgˇ»hi
 5717
 5718           j«kˇ»
 5719           n«lmoˇ»
 5720           "#
 5721    ));
 5722    cx.update_editor(|editor, window, cx| {
 5723        editor.add_selection_above(&Default::default(), window, cx);
 5724    });
 5725
 5726    cx.assert_editor_state(indoc!(
 5727        r#"a«bcˇ»
 5728           d«efgˇ»hi
 5729
 5730           j«kˇ»
 5731           nlmo
 5732           "#
 5733    ));
 5734
 5735    // Change selections again
 5736    cx.set_state(indoc!(
 5737        r#"abc
 5738           d«ˇefghi
 5739
 5740           jk
 5741           nlm»o
 5742           "#
 5743    ));
 5744
 5745    cx.update_editor(|editor, window, cx| {
 5746        editor.add_selection_above(&Default::default(), window, cx);
 5747    });
 5748
 5749    cx.assert_editor_state(indoc!(
 5750        r#"a«ˇbc»
 5751           d«ˇef»ghi
 5752
 5753           j«ˇk»
 5754           n«ˇlm»o
 5755           "#
 5756    ));
 5757
 5758    cx.update_editor(|editor, window, cx| {
 5759        editor.add_selection_below(&Default::default(), window, cx);
 5760    });
 5761
 5762    cx.assert_editor_state(indoc!(
 5763        r#"abc
 5764           d«ˇef»ghi
 5765
 5766           j«ˇk»
 5767           n«ˇlm»o
 5768           "#
 5769    ));
 5770}
 5771
 5772#[gpui::test]
 5773async fn test_select_next(cx: &mut TestAppContext) {
 5774    init_test(cx, |_| {});
 5775
 5776    let mut cx = EditorTestContext::new(cx).await;
 5777    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5778
 5779    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5780        .unwrap();
 5781    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5782
 5783    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5784        .unwrap();
 5785    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5786
 5787    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5788    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5789
 5790    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5791    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5792
 5793    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5794        .unwrap();
 5795    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5796
 5797    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5798        .unwrap();
 5799    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5800}
 5801
 5802#[gpui::test]
 5803async fn test_select_all_matches(cx: &mut TestAppContext) {
 5804    init_test(cx, |_| {});
 5805
 5806    let mut cx = EditorTestContext::new(cx).await;
 5807
 5808    // Test caret-only selections
 5809    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5810    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5811        .unwrap();
 5812    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5813
 5814    // Test left-to-right selections
 5815    cx.set_state("abc\n«abcˇ»\nabc");
 5816    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5817        .unwrap();
 5818    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 5819
 5820    // Test right-to-left selections
 5821    cx.set_state("abc\n«ˇabc»\nabc");
 5822    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5823        .unwrap();
 5824    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 5825
 5826    // Test selecting whitespace with caret selection
 5827    cx.set_state("abc\nˇ   abc\nabc");
 5828    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5829        .unwrap();
 5830    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 5831
 5832    // Test selecting whitespace with left-to-right selection
 5833    cx.set_state("abc\n«ˇ  »abc\nabc");
 5834    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5835        .unwrap();
 5836    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 5837
 5838    // Test no matches with right-to-left selection
 5839    cx.set_state("abc\n«  ˇ»abc\nabc");
 5840    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5841        .unwrap();
 5842    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 5843}
 5844
 5845#[gpui::test]
 5846async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 5847    init_test(cx, |_| {});
 5848
 5849    let mut cx = EditorTestContext::new(cx).await;
 5850
 5851    let large_body_1 = "\nd".repeat(200);
 5852    let large_body_2 = "\ne".repeat(200);
 5853
 5854    cx.set_state(&format!(
 5855        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 5856    ));
 5857    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 5858        let scroll_position = editor.scroll_position(cx);
 5859        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 5860        scroll_position
 5861    });
 5862
 5863    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5864        .unwrap();
 5865    cx.assert_editor_state(&format!(
 5866        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 5867    ));
 5868    let scroll_position_after_selection =
 5869        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 5870    assert_eq!(
 5871        initial_scroll_position, scroll_position_after_selection,
 5872        "Scroll position should not change after selecting all matches"
 5873    );
 5874}
 5875
 5876#[gpui::test]
 5877async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 5878    init_test(cx, |_| {});
 5879
 5880    let mut cx = EditorTestContext::new(cx).await;
 5881    cx.set_state(
 5882        r#"let foo = 2;
 5883lˇet foo = 2;
 5884let fooˇ = 2;
 5885let foo = 2;
 5886let foo = ˇ2;"#,
 5887    );
 5888
 5889    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5890        .unwrap();
 5891    cx.assert_editor_state(
 5892        r#"let foo = 2;
 5893«letˇ» foo = 2;
 5894let «fooˇ» = 2;
 5895let foo = 2;
 5896let foo = «2ˇ»;"#,
 5897    );
 5898
 5899    // noop for multiple selections with different contents
 5900    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5901        .unwrap();
 5902    cx.assert_editor_state(
 5903        r#"let foo = 2;
 5904«letˇ» foo = 2;
 5905let «fooˇ» = 2;
 5906let foo = 2;
 5907let foo = «2ˇ»;"#,
 5908    );
 5909}
 5910
 5911#[gpui::test]
 5912async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 5913    init_test(cx, |_| {});
 5914
 5915    let mut cx =
 5916        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 5917
 5918    cx.assert_editor_state(indoc! {"
 5919        ˇbbb
 5920        ccc
 5921
 5922        bbb
 5923        ccc
 5924        "});
 5925    cx.dispatch_action(SelectPrevious::default());
 5926    cx.assert_editor_state(indoc! {"
 5927                «bbbˇ»
 5928                ccc
 5929
 5930                bbb
 5931                ccc
 5932                "});
 5933    cx.dispatch_action(SelectPrevious::default());
 5934    cx.assert_editor_state(indoc! {"
 5935                «bbbˇ»
 5936                ccc
 5937
 5938                «bbbˇ»
 5939                ccc
 5940                "});
 5941}
 5942
 5943#[gpui::test]
 5944async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 5945    init_test(cx, |_| {});
 5946
 5947    let mut cx = EditorTestContext::new(cx).await;
 5948    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5949
 5950    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5951        .unwrap();
 5952    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5953
 5954    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5955        .unwrap();
 5956    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 5957
 5958    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5959    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5960
 5961    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5962    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 5963
 5964    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5965        .unwrap();
 5966    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 5967
 5968    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5969        .unwrap();
 5970    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 5971
 5972    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5973        .unwrap();
 5974    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 5975}
 5976
 5977#[gpui::test]
 5978async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 5979    init_test(cx, |_| {});
 5980
 5981    let mut cx = EditorTestContext::new(cx).await;
 5982    cx.set_state("");
 5983
 5984    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5985        .unwrap();
 5986    cx.assert_editor_state("«aˇ»");
 5987    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5988        .unwrap();
 5989    cx.assert_editor_state("«aˇ»");
 5990}
 5991
 5992#[gpui::test]
 5993async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 5994    init_test(cx, |_| {});
 5995
 5996    let mut cx = EditorTestContext::new(cx).await;
 5997    cx.set_state(
 5998        r#"let foo = 2;
 5999lˇet foo = 2;
 6000let fooˇ = 2;
 6001let foo = 2;
 6002let foo = ˇ2;"#,
 6003    );
 6004
 6005    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6006        .unwrap();
 6007    cx.assert_editor_state(
 6008        r#"let foo = 2;
 6009«letˇ» foo = 2;
 6010let «fooˇ» = 2;
 6011let foo = 2;
 6012let foo = «2ˇ»;"#,
 6013    );
 6014
 6015    // noop for multiple selections with different contents
 6016    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6017        .unwrap();
 6018    cx.assert_editor_state(
 6019        r#"let foo = 2;
 6020«letˇ» foo = 2;
 6021let «fooˇ» = 2;
 6022let foo = 2;
 6023let foo = «2ˇ»;"#,
 6024    );
 6025}
 6026
 6027#[gpui::test]
 6028async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6029    init_test(cx, |_| {});
 6030
 6031    let mut cx = EditorTestContext::new(cx).await;
 6032    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6033
 6034    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6035        .unwrap();
 6036    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6037
 6038    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6039        .unwrap();
 6040    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6041
 6042    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6043    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6044
 6045    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6046    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6047
 6048    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6049        .unwrap();
 6050    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 6051
 6052    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6053        .unwrap();
 6054    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 6055}
 6056
 6057#[gpui::test]
 6058async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6059    init_test(cx, |_| {});
 6060
 6061    let language = Arc::new(Language::new(
 6062        LanguageConfig::default(),
 6063        Some(tree_sitter_rust::LANGUAGE.into()),
 6064    ));
 6065
 6066    let text = r#"
 6067        use mod1::mod2::{mod3, mod4};
 6068
 6069        fn fn_1(param1: bool, param2: &str) {
 6070            let var1 = "text";
 6071        }
 6072    "#
 6073    .unindent();
 6074
 6075    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6076    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6077    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6078
 6079    editor
 6080        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6081        .await;
 6082
 6083    editor.update_in(cx, |editor, window, cx| {
 6084        editor.change_selections(None, window, cx, |s| {
 6085            s.select_display_ranges([
 6086                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6087                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6088                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6089            ]);
 6090        });
 6091        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6092    });
 6093    editor.update(cx, |editor, cx| {
 6094        assert_text_with_selections(
 6095            editor,
 6096            indoc! {r#"
 6097                use mod1::mod2::{mod3, «mod4ˇ»};
 6098
 6099                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6100                    let var1 = "«ˇtext»";
 6101                }
 6102            "#},
 6103            cx,
 6104        );
 6105    });
 6106
 6107    editor.update_in(cx, |editor, window, cx| {
 6108        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6109    });
 6110    editor.update(cx, |editor, cx| {
 6111        assert_text_with_selections(
 6112            editor,
 6113            indoc! {r#"
 6114                use mod1::mod2::«{mod3, mod4}ˇ»;
 6115
 6116                «ˇfn fn_1(param1: bool, param2: &str) {
 6117                    let var1 = "text";
 6118 6119            "#},
 6120            cx,
 6121        );
 6122    });
 6123
 6124    editor.update_in(cx, |editor, window, cx| {
 6125        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6126    });
 6127    assert_eq!(
 6128        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6129        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6130    );
 6131
 6132    // Trying to expand the selected syntax node one more time has no effect.
 6133    editor.update_in(cx, |editor, window, cx| {
 6134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6135    });
 6136    assert_eq!(
 6137        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6138        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6139    );
 6140
 6141    editor.update_in(cx, |editor, window, cx| {
 6142        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6143    });
 6144    editor.update(cx, |editor, cx| {
 6145        assert_text_with_selections(
 6146            editor,
 6147            indoc! {r#"
 6148                use mod1::mod2::«{mod3, mod4}ˇ»;
 6149
 6150                «ˇfn fn_1(param1: bool, param2: &str) {
 6151                    let var1 = "text";
 6152 6153            "#},
 6154            cx,
 6155        );
 6156    });
 6157
 6158    editor.update_in(cx, |editor, window, cx| {
 6159        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6160    });
 6161    editor.update(cx, |editor, cx| {
 6162        assert_text_with_selections(
 6163            editor,
 6164            indoc! {r#"
 6165                use mod1::mod2::{mod3, «mod4ˇ»};
 6166
 6167                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6168                    let var1 = "«ˇtext»";
 6169                }
 6170            "#},
 6171            cx,
 6172        );
 6173    });
 6174
 6175    editor.update_in(cx, |editor, window, cx| {
 6176        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6177    });
 6178    editor.update(cx, |editor, cx| {
 6179        assert_text_with_selections(
 6180            editor,
 6181            indoc! {r#"
 6182                use mod1::mod2::{mod3, mo«ˇ»d4};
 6183
 6184                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6185                    let var1 = "te«ˇ»xt";
 6186                }
 6187            "#},
 6188            cx,
 6189        );
 6190    });
 6191
 6192    // Trying to shrink the selected syntax node one more time has no effect.
 6193    editor.update_in(cx, |editor, window, cx| {
 6194        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6195    });
 6196    editor.update_in(cx, |editor, _, cx| {
 6197        assert_text_with_selections(
 6198            editor,
 6199            indoc! {r#"
 6200                use mod1::mod2::{mod3, mo«ˇ»d4};
 6201
 6202                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6203                    let var1 = "te«ˇ»xt";
 6204                }
 6205            "#},
 6206            cx,
 6207        );
 6208    });
 6209
 6210    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6211    // a fold.
 6212    editor.update_in(cx, |editor, window, cx| {
 6213        editor.fold_creases(
 6214            vec![
 6215                Crease::simple(
 6216                    Point::new(0, 21)..Point::new(0, 24),
 6217                    FoldPlaceholder::test(),
 6218                ),
 6219                Crease::simple(
 6220                    Point::new(3, 20)..Point::new(3, 22),
 6221                    FoldPlaceholder::test(),
 6222                ),
 6223            ],
 6224            true,
 6225            window,
 6226            cx,
 6227        );
 6228        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6229    });
 6230    editor.update(cx, |editor, cx| {
 6231        assert_text_with_selections(
 6232            editor,
 6233            indoc! {r#"
 6234                use mod1::mod2::«{mod3, mod4}ˇ»;
 6235
 6236                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6237                    «ˇlet var1 = "text";»
 6238                }
 6239            "#},
 6240            cx,
 6241        );
 6242    });
 6243}
 6244
 6245#[gpui::test]
 6246async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6247    init_test(cx, |_| {});
 6248
 6249    let base_text = r#"
 6250        impl A {
 6251            // this is an uncommitted comment
 6252
 6253            fn b() {
 6254                c();
 6255            }
 6256
 6257            // this is another uncommitted comment
 6258
 6259            fn d() {
 6260                // e
 6261                // f
 6262            }
 6263        }
 6264
 6265        fn g() {
 6266            // h
 6267        }
 6268    "#
 6269    .unindent();
 6270
 6271    let text = r#"
 6272        ˇimpl A {
 6273
 6274            fn b() {
 6275                c();
 6276            }
 6277
 6278            fn d() {
 6279                // e
 6280                // f
 6281            }
 6282        }
 6283
 6284        fn g() {
 6285            // h
 6286        }
 6287    "#
 6288    .unindent();
 6289
 6290    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6291    cx.set_state(&text);
 6292    cx.set_head_text(&base_text);
 6293    cx.update_editor(|editor, window, cx| {
 6294        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6295    });
 6296
 6297    cx.assert_state_with_diff(
 6298        "
 6299        ˇimpl A {
 6300      -     // this is an uncommitted comment
 6301
 6302            fn b() {
 6303                c();
 6304            }
 6305
 6306      -     // this is another uncommitted comment
 6307      -
 6308            fn d() {
 6309                // e
 6310                // f
 6311            }
 6312        }
 6313
 6314        fn g() {
 6315            // h
 6316        }
 6317    "
 6318        .unindent(),
 6319    );
 6320
 6321    let expected_display_text = "
 6322        impl A {
 6323            // this is an uncommitted comment
 6324
 6325            fn b() {
 6326 6327            }
 6328
 6329            // this is another uncommitted comment
 6330
 6331            fn d() {
 6332 6333            }
 6334        }
 6335
 6336        fn g() {
 6337 6338        }
 6339        "
 6340    .unindent();
 6341
 6342    cx.update_editor(|editor, window, cx| {
 6343        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 6344        assert_eq!(editor.display_text(cx), expected_display_text);
 6345    });
 6346}
 6347
 6348#[gpui::test]
 6349async fn test_autoindent(cx: &mut TestAppContext) {
 6350    init_test(cx, |_| {});
 6351
 6352    let language = Arc::new(
 6353        Language::new(
 6354            LanguageConfig {
 6355                brackets: BracketPairConfig {
 6356                    pairs: vec![
 6357                        BracketPair {
 6358                            start: "{".to_string(),
 6359                            end: "}".to_string(),
 6360                            close: false,
 6361                            surround: false,
 6362                            newline: true,
 6363                        },
 6364                        BracketPair {
 6365                            start: "(".to_string(),
 6366                            end: ")".to_string(),
 6367                            close: false,
 6368                            surround: false,
 6369                            newline: true,
 6370                        },
 6371                    ],
 6372                    ..Default::default()
 6373                },
 6374                ..Default::default()
 6375            },
 6376            Some(tree_sitter_rust::LANGUAGE.into()),
 6377        )
 6378        .with_indents_query(
 6379            r#"
 6380                (_ "(" ")" @end) @indent
 6381                (_ "{" "}" @end) @indent
 6382            "#,
 6383        )
 6384        .unwrap(),
 6385    );
 6386
 6387    let text = "fn a() {}";
 6388
 6389    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6390    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6391    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6392    editor
 6393        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6394        .await;
 6395
 6396    editor.update_in(cx, |editor, window, cx| {
 6397        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 6398        editor.newline(&Newline, window, cx);
 6399        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 6400        assert_eq!(
 6401            editor.selections.ranges(cx),
 6402            &[
 6403                Point::new(1, 4)..Point::new(1, 4),
 6404                Point::new(3, 4)..Point::new(3, 4),
 6405                Point::new(5, 0)..Point::new(5, 0)
 6406            ]
 6407        );
 6408    });
 6409}
 6410
 6411#[gpui::test]
 6412async fn test_autoindent_selections(cx: &mut TestAppContext) {
 6413    init_test(cx, |_| {});
 6414
 6415    {
 6416        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6417        cx.set_state(indoc! {"
 6418            impl A {
 6419
 6420                fn b() {}
 6421
 6422            «fn c() {
 6423
 6424            }ˇ»
 6425            }
 6426        "});
 6427
 6428        cx.update_editor(|editor, window, cx| {
 6429            editor.autoindent(&Default::default(), window, cx);
 6430        });
 6431
 6432        cx.assert_editor_state(indoc! {"
 6433            impl A {
 6434
 6435                fn b() {}
 6436
 6437                «fn c() {
 6438
 6439                }ˇ»
 6440            }
 6441        "});
 6442    }
 6443
 6444    {
 6445        let mut cx = EditorTestContext::new_multibuffer(
 6446            cx,
 6447            [indoc! { "
 6448                impl A {
 6449                «
 6450                // a
 6451                fn b(){}
 6452                »
 6453                «
 6454                    }
 6455                    fn c(){}
 6456                »
 6457            "}],
 6458        );
 6459
 6460        let buffer = cx.update_editor(|editor, _, cx| {
 6461            let buffer = editor.buffer().update(cx, |buffer, _| {
 6462                buffer.all_buffers().iter().next().unwrap().clone()
 6463            });
 6464            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6465            buffer
 6466        });
 6467
 6468        cx.run_until_parked();
 6469        cx.update_editor(|editor, window, cx| {
 6470            editor.select_all(&Default::default(), window, cx);
 6471            editor.autoindent(&Default::default(), window, cx)
 6472        });
 6473        cx.run_until_parked();
 6474
 6475        cx.update(|_, cx| {
 6476            assert_eq!(
 6477                buffer.read(cx).text(),
 6478                indoc! { "
 6479                    impl A {
 6480
 6481                        // a
 6482                        fn b(){}
 6483
 6484
 6485                    }
 6486                    fn c(){}
 6487
 6488                " }
 6489            )
 6490        });
 6491    }
 6492}
 6493
 6494#[gpui::test]
 6495async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 6496    init_test(cx, |_| {});
 6497
 6498    let mut cx = EditorTestContext::new(cx).await;
 6499
 6500    let language = Arc::new(Language::new(
 6501        LanguageConfig {
 6502            brackets: BracketPairConfig {
 6503                pairs: vec![
 6504                    BracketPair {
 6505                        start: "{".to_string(),
 6506                        end: "}".to_string(),
 6507                        close: true,
 6508                        surround: true,
 6509                        newline: true,
 6510                    },
 6511                    BracketPair {
 6512                        start: "(".to_string(),
 6513                        end: ")".to_string(),
 6514                        close: true,
 6515                        surround: true,
 6516                        newline: true,
 6517                    },
 6518                    BracketPair {
 6519                        start: "/*".to_string(),
 6520                        end: " */".to_string(),
 6521                        close: true,
 6522                        surround: true,
 6523                        newline: true,
 6524                    },
 6525                    BracketPair {
 6526                        start: "[".to_string(),
 6527                        end: "]".to_string(),
 6528                        close: false,
 6529                        surround: false,
 6530                        newline: true,
 6531                    },
 6532                    BracketPair {
 6533                        start: "\"".to_string(),
 6534                        end: "\"".to_string(),
 6535                        close: true,
 6536                        surround: true,
 6537                        newline: false,
 6538                    },
 6539                    BracketPair {
 6540                        start: "<".to_string(),
 6541                        end: ">".to_string(),
 6542                        close: false,
 6543                        surround: true,
 6544                        newline: true,
 6545                    },
 6546                ],
 6547                ..Default::default()
 6548            },
 6549            autoclose_before: "})]".to_string(),
 6550            ..Default::default()
 6551        },
 6552        Some(tree_sitter_rust::LANGUAGE.into()),
 6553    ));
 6554
 6555    cx.language_registry().add(language.clone());
 6556    cx.update_buffer(|buffer, cx| {
 6557        buffer.set_language(Some(language), cx);
 6558    });
 6559
 6560    cx.set_state(
 6561        &r#"
 6562            🏀ˇ
 6563            εˇ
 6564            ❤️ˇ
 6565        "#
 6566        .unindent(),
 6567    );
 6568
 6569    // autoclose multiple nested brackets at multiple cursors
 6570    cx.update_editor(|editor, window, cx| {
 6571        editor.handle_input("{", window, cx);
 6572        editor.handle_input("{", window, cx);
 6573        editor.handle_input("{", window, cx);
 6574    });
 6575    cx.assert_editor_state(
 6576        &"
 6577            🏀{{{ˇ}}}
 6578            ε{{{ˇ}}}
 6579            ❤️{{{ˇ}}}
 6580        "
 6581        .unindent(),
 6582    );
 6583
 6584    // insert a different closing bracket
 6585    cx.update_editor(|editor, window, cx| {
 6586        editor.handle_input(")", window, cx);
 6587    });
 6588    cx.assert_editor_state(
 6589        &"
 6590            🏀{{{)ˇ}}}
 6591            ε{{{)ˇ}}}
 6592            ❤️{{{)ˇ}}}
 6593        "
 6594        .unindent(),
 6595    );
 6596
 6597    // skip over the auto-closed brackets when typing a closing bracket
 6598    cx.update_editor(|editor, window, cx| {
 6599        editor.move_right(&MoveRight, window, cx);
 6600        editor.handle_input("}", window, cx);
 6601        editor.handle_input("}", window, cx);
 6602        editor.handle_input("}", window, cx);
 6603    });
 6604    cx.assert_editor_state(
 6605        &"
 6606            🏀{{{)}}}}ˇ
 6607            ε{{{)}}}}ˇ
 6608            ❤️{{{)}}}}ˇ
 6609        "
 6610        .unindent(),
 6611    );
 6612
 6613    // autoclose multi-character pairs
 6614    cx.set_state(
 6615        &"
 6616            ˇ
 6617            ˇ
 6618        "
 6619        .unindent(),
 6620    );
 6621    cx.update_editor(|editor, window, cx| {
 6622        editor.handle_input("/", window, cx);
 6623        editor.handle_input("*", window, cx);
 6624    });
 6625    cx.assert_editor_state(
 6626        &"
 6627            /*ˇ */
 6628            /*ˇ */
 6629        "
 6630        .unindent(),
 6631    );
 6632
 6633    // one cursor autocloses a multi-character pair, one cursor
 6634    // does not autoclose.
 6635    cx.set_state(
 6636        &"
 6637 6638            ˇ
 6639        "
 6640        .unindent(),
 6641    );
 6642    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 6643    cx.assert_editor_state(
 6644        &"
 6645            /*ˇ */
 6646 6647        "
 6648        .unindent(),
 6649    );
 6650
 6651    // Don't autoclose if the next character isn't whitespace and isn't
 6652    // listed in the language's "autoclose_before" section.
 6653    cx.set_state("ˇa b");
 6654    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6655    cx.assert_editor_state("{ˇa b");
 6656
 6657    // Don't autoclose if `close` is false for the bracket pair
 6658    cx.set_state("ˇ");
 6659    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 6660    cx.assert_editor_state("");
 6661
 6662    // Surround with brackets if text is selected
 6663    cx.set_state("«aˇ» b");
 6664    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6665    cx.assert_editor_state("{«aˇ»} b");
 6666
 6667    // Autoclose when not immediately after a word character
 6668    cx.set_state("a ˇ");
 6669    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6670    cx.assert_editor_state("a \"ˇ\"");
 6671
 6672    // Autoclose pair where the start and end characters are the same
 6673    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6674    cx.assert_editor_state("a \"\"ˇ");
 6675
 6676    // Don't autoclose when immediately after a word character
 6677    cx.set_state("");
 6678    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6679    cx.assert_editor_state("a\"ˇ");
 6680
 6681    // Do autoclose when after a non-word character
 6682    cx.set_state("");
 6683    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6684    cx.assert_editor_state("{\"ˇ\"");
 6685
 6686    // Non identical pairs autoclose regardless of preceding character
 6687    cx.set_state("");
 6688    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6689    cx.assert_editor_state("a{ˇ}");
 6690
 6691    // Don't autoclose pair if autoclose is disabled
 6692    cx.set_state("ˇ");
 6693    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6694    cx.assert_editor_state("");
 6695
 6696    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 6697    cx.set_state("«aˇ» b");
 6698    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6699    cx.assert_editor_state("<«aˇ»> b");
 6700}
 6701
 6702#[gpui::test]
 6703async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 6704    init_test(cx, |settings| {
 6705        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 6706    });
 6707
 6708    let mut cx = EditorTestContext::new(cx).await;
 6709
 6710    let language = Arc::new(Language::new(
 6711        LanguageConfig {
 6712            brackets: BracketPairConfig {
 6713                pairs: vec![
 6714                    BracketPair {
 6715                        start: "{".to_string(),
 6716                        end: "}".to_string(),
 6717                        close: true,
 6718                        surround: true,
 6719                        newline: true,
 6720                    },
 6721                    BracketPair {
 6722                        start: "(".to_string(),
 6723                        end: ")".to_string(),
 6724                        close: true,
 6725                        surround: true,
 6726                        newline: true,
 6727                    },
 6728                    BracketPair {
 6729                        start: "[".to_string(),
 6730                        end: "]".to_string(),
 6731                        close: false,
 6732                        surround: false,
 6733                        newline: true,
 6734                    },
 6735                ],
 6736                ..Default::default()
 6737            },
 6738            autoclose_before: "})]".to_string(),
 6739            ..Default::default()
 6740        },
 6741        Some(tree_sitter_rust::LANGUAGE.into()),
 6742    ));
 6743
 6744    cx.language_registry().add(language.clone());
 6745    cx.update_buffer(|buffer, cx| {
 6746        buffer.set_language(Some(language), cx);
 6747    });
 6748
 6749    cx.set_state(
 6750        &"
 6751            ˇ
 6752            ˇ
 6753            ˇ
 6754        "
 6755        .unindent(),
 6756    );
 6757
 6758    // ensure only matching closing brackets are skipped over
 6759    cx.update_editor(|editor, window, cx| {
 6760        editor.handle_input("}", window, cx);
 6761        editor.move_left(&MoveLeft, window, cx);
 6762        editor.handle_input(")", window, cx);
 6763        editor.move_left(&MoveLeft, window, cx);
 6764    });
 6765    cx.assert_editor_state(
 6766        &"
 6767            ˇ)}
 6768            ˇ)}
 6769            ˇ)}
 6770        "
 6771        .unindent(),
 6772    );
 6773
 6774    // skip-over closing brackets at multiple cursors
 6775    cx.update_editor(|editor, window, cx| {
 6776        editor.handle_input(")", window, cx);
 6777        editor.handle_input("}", window, cx);
 6778    });
 6779    cx.assert_editor_state(
 6780        &"
 6781            )}ˇ
 6782            )}ˇ
 6783            )}ˇ
 6784        "
 6785        .unindent(),
 6786    );
 6787
 6788    // ignore non-close brackets
 6789    cx.update_editor(|editor, window, cx| {
 6790        editor.handle_input("]", window, cx);
 6791        editor.move_left(&MoveLeft, window, cx);
 6792        editor.handle_input("]", window, cx);
 6793    });
 6794    cx.assert_editor_state(
 6795        &"
 6796            )}]ˇ]
 6797            )}]ˇ]
 6798            )}]ˇ]
 6799        "
 6800        .unindent(),
 6801    );
 6802}
 6803
 6804#[gpui::test]
 6805async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 6806    init_test(cx, |_| {});
 6807
 6808    let mut cx = EditorTestContext::new(cx).await;
 6809
 6810    let html_language = Arc::new(
 6811        Language::new(
 6812            LanguageConfig {
 6813                name: "HTML".into(),
 6814                brackets: BracketPairConfig {
 6815                    pairs: vec![
 6816                        BracketPair {
 6817                            start: "<".into(),
 6818                            end: ">".into(),
 6819                            close: true,
 6820                            ..Default::default()
 6821                        },
 6822                        BracketPair {
 6823                            start: "{".into(),
 6824                            end: "}".into(),
 6825                            close: true,
 6826                            ..Default::default()
 6827                        },
 6828                        BracketPair {
 6829                            start: "(".into(),
 6830                            end: ")".into(),
 6831                            close: true,
 6832                            ..Default::default()
 6833                        },
 6834                    ],
 6835                    ..Default::default()
 6836                },
 6837                autoclose_before: "})]>".into(),
 6838                ..Default::default()
 6839            },
 6840            Some(tree_sitter_html::LANGUAGE.into()),
 6841        )
 6842        .with_injection_query(
 6843            r#"
 6844            (script_element
 6845                (raw_text) @injection.content
 6846                (#set! injection.language "javascript"))
 6847            "#,
 6848        )
 6849        .unwrap(),
 6850    );
 6851
 6852    let javascript_language = Arc::new(Language::new(
 6853        LanguageConfig {
 6854            name: "JavaScript".into(),
 6855            brackets: BracketPairConfig {
 6856                pairs: vec![
 6857                    BracketPair {
 6858                        start: "/*".into(),
 6859                        end: " */".into(),
 6860                        close: true,
 6861                        ..Default::default()
 6862                    },
 6863                    BracketPair {
 6864                        start: "{".into(),
 6865                        end: "}".into(),
 6866                        close: true,
 6867                        ..Default::default()
 6868                    },
 6869                    BracketPair {
 6870                        start: "(".into(),
 6871                        end: ")".into(),
 6872                        close: true,
 6873                        ..Default::default()
 6874                    },
 6875                ],
 6876                ..Default::default()
 6877            },
 6878            autoclose_before: "})]>".into(),
 6879            ..Default::default()
 6880        },
 6881        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 6882    ));
 6883
 6884    cx.language_registry().add(html_language.clone());
 6885    cx.language_registry().add(javascript_language.clone());
 6886
 6887    cx.update_buffer(|buffer, cx| {
 6888        buffer.set_language(Some(html_language), cx);
 6889    });
 6890
 6891    cx.set_state(
 6892        &r#"
 6893            <body>ˇ
 6894                <script>
 6895                    var x = 1;ˇ
 6896                </script>
 6897            </body>ˇ
 6898        "#
 6899        .unindent(),
 6900    );
 6901
 6902    // Precondition: different languages are active at different locations.
 6903    cx.update_editor(|editor, window, cx| {
 6904        let snapshot = editor.snapshot(window, cx);
 6905        let cursors = editor.selections.ranges::<usize>(cx);
 6906        let languages = cursors
 6907            .iter()
 6908            .map(|c| snapshot.language_at(c.start).unwrap().name())
 6909            .collect::<Vec<_>>();
 6910        assert_eq!(
 6911            languages,
 6912            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 6913        );
 6914    });
 6915
 6916    // Angle brackets autoclose in HTML, but not JavaScript.
 6917    cx.update_editor(|editor, window, cx| {
 6918        editor.handle_input("<", window, cx);
 6919        editor.handle_input("a", window, cx);
 6920    });
 6921    cx.assert_editor_state(
 6922        &r#"
 6923            <body><aˇ>
 6924                <script>
 6925                    var x = 1;<aˇ
 6926                </script>
 6927            </body><aˇ>
 6928        "#
 6929        .unindent(),
 6930    );
 6931
 6932    // Curly braces and parens autoclose in both HTML and JavaScript.
 6933    cx.update_editor(|editor, window, cx| {
 6934        editor.handle_input(" b=", window, cx);
 6935        editor.handle_input("{", window, cx);
 6936        editor.handle_input("c", window, cx);
 6937        editor.handle_input("(", window, cx);
 6938    });
 6939    cx.assert_editor_state(
 6940        &r#"
 6941            <body><a b={c(ˇ)}>
 6942                <script>
 6943                    var x = 1;<a b={c(ˇ)}
 6944                </script>
 6945            </body><a b={c(ˇ)}>
 6946        "#
 6947        .unindent(),
 6948    );
 6949
 6950    // Brackets that were already autoclosed are skipped.
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.handle_input(")", window, cx);
 6953        editor.handle_input("d", window, cx);
 6954        editor.handle_input("}", window, cx);
 6955    });
 6956    cx.assert_editor_state(
 6957        &r#"
 6958            <body><a b={c()d}ˇ>
 6959                <script>
 6960                    var x = 1;<a b={c()d}ˇ
 6961                </script>
 6962            </body><a b={c()d}ˇ>
 6963        "#
 6964        .unindent(),
 6965    );
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.handle_input(">", window, cx);
 6968    });
 6969    cx.assert_editor_state(
 6970        &r#"
 6971            <body><a b={c()d}>ˇ
 6972                <script>
 6973                    var x = 1;<a b={c()d}>ˇ
 6974                </script>
 6975            </body><a b={c()d}>ˇ
 6976        "#
 6977        .unindent(),
 6978    );
 6979
 6980    // Reset
 6981    cx.set_state(
 6982        &r#"
 6983            <body>ˇ
 6984                <script>
 6985                    var x = 1;ˇ
 6986                </script>
 6987            </body>ˇ
 6988        "#
 6989        .unindent(),
 6990    );
 6991
 6992    cx.update_editor(|editor, window, cx| {
 6993        editor.handle_input("<", window, cx);
 6994    });
 6995    cx.assert_editor_state(
 6996        &r#"
 6997            <body><ˇ>
 6998                <script>
 6999                    var x = 1;<ˇ
 7000                </script>
 7001            </body><ˇ>
 7002        "#
 7003        .unindent(),
 7004    );
 7005
 7006    // When backspacing, the closing angle brackets are removed.
 7007    cx.update_editor(|editor, window, cx| {
 7008        editor.backspace(&Backspace, window, cx);
 7009    });
 7010    cx.assert_editor_state(
 7011        &r#"
 7012            <body>ˇ
 7013                <script>
 7014                    var x = 1;ˇ
 7015                </script>
 7016            </body>ˇ
 7017        "#
 7018        .unindent(),
 7019    );
 7020
 7021    // Block comments autoclose in JavaScript, but not HTML.
 7022    cx.update_editor(|editor, window, cx| {
 7023        editor.handle_input("/", window, cx);
 7024        editor.handle_input("*", window, cx);
 7025    });
 7026    cx.assert_editor_state(
 7027        &r#"
 7028            <body>/*ˇ
 7029                <script>
 7030                    var x = 1;/*ˇ */
 7031                </script>
 7032            </body>/*ˇ
 7033        "#
 7034        .unindent(),
 7035    );
 7036}
 7037
 7038#[gpui::test]
 7039async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7040    init_test(cx, |_| {});
 7041
 7042    let mut cx = EditorTestContext::new(cx).await;
 7043
 7044    let rust_language = Arc::new(
 7045        Language::new(
 7046            LanguageConfig {
 7047                name: "Rust".into(),
 7048                brackets: serde_json::from_value(json!([
 7049                    { "start": "{", "end": "}", "close": true, "newline": true },
 7050                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7051                ]))
 7052                .unwrap(),
 7053                autoclose_before: "})]>".into(),
 7054                ..Default::default()
 7055            },
 7056            Some(tree_sitter_rust::LANGUAGE.into()),
 7057        )
 7058        .with_override_query("(string_literal) @string")
 7059        .unwrap(),
 7060    );
 7061
 7062    cx.language_registry().add(rust_language.clone());
 7063    cx.update_buffer(|buffer, cx| {
 7064        buffer.set_language(Some(rust_language), cx);
 7065    });
 7066
 7067    cx.set_state(
 7068        &r#"
 7069            let x = ˇ
 7070        "#
 7071        .unindent(),
 7072    );
 7073
 7074    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7075    cx.update_editor(|editor, window, cx| {
 7076        editor.handle_input("\"", window, cx);
 7077    });
 7078    cx.assert_editor_state(
 7079        &r#"
 7080            let x = "ˇ"
 7081        "#
 7082        .unindent(),
 7083    );
 7084
 7085    // Inserting another quotation mark. The cursor moves across the existing
 7086    // automatically-inserted quotation mark.
 7087    cx.update_editor(|editor, window, cx| {
 7088        editor.handle_input("\"", window, cx);
 7089    });
 7090    cx.assert_editor_state(
 7091        &r#"
 7092            let x = ""ˇ
 7093        "#
 7094        .unindent(),
 7095    );
 7096
 7097    // Reset
 7098    cx.set_state(
 7099        &r#"
 7100            let x = ˇ
 7101        "#
 7102        .unindent(),
 7103    );
 7104
 7105    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7106    cx.update_editor(|editor, window, cx| {
 7107        editor.handle_input("\"", window, cx);
 7108        editor.handle_input(" ", window, cx);
 7109        editor.move_left(&Default::default(), window, cx);
 7110        editor.handle_input("\\", window, cx);
 7111        editor.handle_input("\"", window, cx);
 7112    });
 7113    cx.assert_editor_state(
 7114        &r#"
 7115            let x = "\"ˇ "
 7116        "#
 7117        .unindent(),
 7118    );
 7119
 7120    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7121    // mark. Nothing is inserted.
 7122    cx.update_editor(|editor, window, cx| {
 7123        editor.move_right(&Default::default(), window, cx);
 7124        editor.handle_input("\"", window, cx);
 7125    });
 7126    cx.assert_editor_state(
 7127        &r#"
 7128            let x = "\" "ˇ
 7129        "#
 7130        .unindent(),
 7131    );
 7132}
 7133
 7134#[gpui::test]
 7135async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7136    init_test(cx, |_| {});
 7137
 7138    let language = Arc::new(Language::new(
 7139        LanguageConfig {
 7140            brackets: BracketPairConfig {
 7141                pairs: vec![
 7142                    BracketPair {
 7143                        start: "{".to_string(),
 7144                        end: "}".to_string(),
 7145                        close: true,
 7146                        surround: true,
 7147                        newline: true,
 7148                    },
 7149                    BracketPair {
 7150                        start: "/* ".to_string(),
 7151                        end: "*/".to_string(),
 7152                        close: true,
 7153                        surround: true,
 7154                        ..Default::default()
 7155                    },
 7156                ],
 7157                ..Default::default()
 7158            },
 7159            ..Default::default()
 7160        },
 7161        Some(tree_sitter_rust::LANGUAGE.into()),
 7162    ));
 7163
 7164    let text = r#"
 7165        a
 7166        b
 7167        c
 7168    "#
 7169    .unindent();
 7170
 7171    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7172    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7173    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7174    editor
 7175        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7176        .await;
 7177
 7178    editor.update_in(cx, |editor, window, cx| {
 7179        editor.change_selections(None, window, cx, |s| {
 7180            s.select_display_ranges([
 7181                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7182                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7183                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7184            ])
 7185        });
 7186
 7187        editor.handle_input("{", window, cx);
 7188        editor.handle_input("{", window, cx);
 7189        editor.handle_input("{", window, cx);
 7190        assert_eq!(
 7191            editor.text(cx),
 7192            "
 7193                {{{a}}}
 7194                {{{b}}}
 7195                {{{c}}}
 7196            "
 7197            .unindent()
 7198        );
 7199        assert_eq!(
 7200            editor.selections.display_ranges(cx),
 7201            [
 7202                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7203                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7204                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7205            ]
 7206        );
 7207
 7208        editor.undo(&Undo, window, cx);
 7209        editor.undo(&Undo, window, cx);
 7210        editor.undo(&Undo, window, cx);
 7211        assert_eq!(
 7212            editor.text(cx),
 7213            "
 7214                a
 7215                b
 7216                c
 7217            "
 7218            .unindent()
 7219        );
 7220        assert_eq!(
 7221            editor.selections.display_ranges(cx),
 7222            [
 7223                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7224                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7225                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7226            ]
 7227        );
 7228
 7229        // Ensure inserting the first character of a multi-byte bracket pair
 7230        // doesn't surround the selections with the bracket.
 7231        editor.handle_input("/", window, cx);
 7232        assert_eq!(
 7233            editor.text(cx),
 7234            "
 7235                /
 7236                /
 7237                /
 7238            "
 7239            .unindent()
 7240        );
 7241        assert_eq!(
 7242            editor.selections.display_ranges(cx),
 7243            [
 7244                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7245                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7246                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7247            ]
 7248        );
 7249
 7250        editor.undo(&Undo, window, cx);
 7251        assert_eq!(
 7252            editor.text(cx),
 7253            "
 7254                a
 7255                b
 7256                c
 7257            "
 7258            .unindent()
 7259        );
 7260        assert_eq!(
 7261            editor.selections.display_ranges(cx),
 7262            [
 7263                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7264                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7265                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7266            ]
 7267        );
 7268
 7269        // Ensure inserting the last character of a multi-byte bracket pair
 7270        // doesn't surround the selections with the bracket.
 7271        editor.handle_input("*", window, cx);
 7272        assert_eq!(
 7273            editor.text(cx),
 7274            "
 7275                *
 7276                *
 7277                *
 7278            "
 7279            .unindent()
 7280        );
 7281        assert_eq!(
 7282            editor.selections.display_ranges(cx),
 7283            [
 7284                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7285                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7286                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7287            ]
 7288        );
 7289    });
 7290}
 7291
 7292#[gpui::test]
 7293async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7294    init_test(cx, |_| {});
 7295
 7296    let language = Arc::new(Language::new(
 7297        LanguageConfig {
 7298            brackets: BracketPairConfig {
 7299                pairs: vec![BracketPair {
 7300                    start: "{".to_string(),
 7301                    end: "}".to_string(),
 7302                    close: true,
 7303                    surround: true,
 7304                    newline: true,
 7305                }],
 7306                ..Default::default()
 7307            },
 7308            autoclose_before: "}".to_string(),
 7309            ..Default::default()
 7310        },
 7311        Some(tree_sitter_rust::LANGUAGE.into()),
 7312    ));
 7313
 7314    let text = r#"
 7315        a
 7316        b
 7317        c
 7318    "#
 7319    .unindent();
 7320
 7321    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7322    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7323    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7324    editor
 7325        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7326        .await;
 7327
 7328    editor.update_in(cx, |editor, window, cx| {
 7329        editor.change_selections(None, window, cx, |s| {
 7330            s.select_ranges([
 7331                Point::new(0, 1)..Point::new(0, 1),
 7332                Point::new(1, 1)..Point::new(1, 1),
 7333                Point::new(2, 1)..Point::new(2, 1),
 7334            ])
 7335        });
 7336
 7337        editor.handle_input("{", window, cx);
 7338        editor.handle_input("{", window, cx);
 7339        editor.handle_input("_", window, cx);
 7340        assert_eq!(
 7341            editor.text(cx),
 7342            "
 7343                a{{_}}
 7344                b{{_}}
 7345                c{{_}}
 7346            "
 7347            .unindent()
 7348        );
 7349        assert_eq!(
 7350            editor.selections.ranges::<Point>(cx),
 7351            [
 7352                Point::new(0, 4)..Point::new(0, 4),
 7353                Point::new(1, 4)..Point::new(1, 4),
 7354                Point::new(2, 4)..Point::new(2, 4)
 7355            ]
 7356        );
 7357
 7358        editor.backspace(&Default::default(), window, cx);
 7359        editor.backspace(&Default::default(), window, cx);
 7360        assert_eq!(
 7361            editor.text(cx),
 7362            "
 7363                a{}
 7364                b{}
 7365                c{}
 7366            "
 7367            .unindent()
 7368        );
 7369        assert_eq!(
 7370            editor.selections.ranges::<Point>(cx),
 7371            [
 7372                Point::new(0, 2)..Point::new(0, 2),
 7373                Point::new(1, 2)..Point::new(1, 2),
 7374                Point::new(2, 2)..Point::new(2, 2)
 7375            ]
 7376        );
 7377
 7378        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 7379        assert_eq!(
 7380            editor.text(cx),
 7381            "
 7382                a
 7383                b
 7384                c
 7385            "
 7386            .unindent()
 7387        );
 7388        assert_eq!(
 7389            editor.selections.ranges::<Point>(cx),
 7390            [
 7391                Point::new(0, 1)..Point::new(0, 1),
 7392                Point::new(1, 1)..Point::new(1, 1),
 7393                Point::new(2, 1)..Point::new(2, 1)
 7394            ]
 7395        );
 7396    });
 7397}
 7398
 7399#[gpui::test]
 7400async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 7401    init_test(cx, |settings| {
 7402        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7403    });
 7404
 7405    let mut cx = EditorTestContext::new(cx).await;
 7406
 7407    let language = Arc::new(Language::new(
 7408        LanguageConfig {
 7409            brackets: BracketPairConfig {
 7410                pairs: vec![
 7411                    BracketPair {
 7412                        start: "{".to_string(),
 7413                        end: "}".to_string(),
 7414                        close: true,
 7415                        surround: true,
 7416                        newline: true,
 7417                    },
 7418                    BracketPair {
 7419                        start: "(".to_string(),
 7420                        end: ")".to_string(),
 7421                        close: true,
 7422                        surround: true,
 7423                        newline: true,
 7424                    },
 7425                    BracketPair {
 7426                        start: "[".to_string(),
 7427                        end: "]".to_string(),
 7428                        close: false,
 7429                        surround: true,
 7430                        newline: true,
 7431                    },
 7432                ],
 7433                ..Default::default()
 7434            },
 7435            autoclose_before: "})]".to_string(),
 7436            ..Default::default()
 7437        },
 7438        Some(tree_sitter_rust::LANGUAGE.into()),
 7439    ));
 7440
 7441    cx.language_registry().add(language.clone());
 7442    cx.update_buffer(|buffer, cx| {
 7443        buffer.set_language(Some(language), cx);
 7444    });
 7445
 7446    cx.set_state(
 7447        &"
 7448            {(ˇ)}
 7449            [[ˇ]]
 7450            {(ˇ)}
 7451        "
 7452        .unindent(),
 7453    );
 7454
 7455    cx.update_editor(|editor, window, cx| {
 7456        editor.backspace(&Default::default(), window, cx);
 7457        editor.backspace(&Default::default(), window, cx);
 7458    });
 7459
 7460    cx.assert_editor_state(
 7461        &"
 7462            ˇ
 7463            ˇ]]
 7464            ˇ
 7465        "
 7466        .unindent(),
 7467    );
 7468
 7469    cx.update_editor(|editor, window, cx| {
 7470        editor.handle_input("{", window, cx);
 7471        editor.handle_input("{", window, cx);
 7472        editor.move_right(&MoveRight, window, cx);
 7473        editor.move_right(&MoveRight, window, cx);
 7474        editor.move_left(&MoveLeft, window, cx);
 7475        editor.move_left(&MoveLeft, window, cx);
 7476        editor.backspace(&Default::default(), window, cx);
 7477    });
 7478
 7479    cx.assert_editor_state(
 7480        &"
 7481            {ˇ}
 7482            {ˇ}]]
 7483            {ˇ}
 7484        "
 7485        .unindent(),
 7486    );
 7487
 7488    cx.update_editor(|editor, window, cx| {
 7489        editor.backspace(&Default::default(), window, cx);
 7490    });
 7491
 7492    cx.assert_editor_state(
 7493        &"
 7494            ˇ
 7495            ˇ]]
 7496            ˇ
 7497        "
 7498        .unindent(),
 7499    );
 7500}
 7501
 7502#[gpui::test]
 7503async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 7504    init_test(cx, |_| {});
 7505
 7506    let language = Arc::new(Language::new(
 7507        LanguageConfig::default(),
 7508        Some(tree_sitter_rust::LANGUAGE.into()),
 7509    ));
 7510
 7511    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 7512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7513    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7514    editor
 7515        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7516        .await;
 7517
 7518    editor.update_in(cx, |editor, window, cx| {
 7519        editor.set_auto_replace_emoji_shortcode(true);
 7520
 7521        editor.handle_input("Hello ", window, cx);
 7522        editor.handle_input(":wave", window, cx);
 7523        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 7524
 7525        editor.handle_input(":", window, cx);
 7526        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 7527
 7528        editor.handle_input(" :smile", window, cx);
 7529        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 7530
 7531        editor.handle_input(":", window, cx);
 7532        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 7533
 7534        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 7535        editor.handle_input(":wave", window, cx);
 7536        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 7537
 7538        editor.handle_input(":", window, cx);
 7539        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 7540
 7541        editor.handle_input(":1", window, cx);
 7542        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 7543
 7544        editor.handle_input(":", window, cx);
 7545        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 7546
 7547        // Ensure shortcode does not get replaced when it is part of a word
 7548        editor.handle_input(" Test:wave", window, cx);
 7549        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 7550
 7551        editor.handle_input(":", window, cx);
 7552        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 7553
 7554        editor.set_auto_replace_emoji_shortcode(false);
 7555
 7556        // Ensure shortcode does not get replaced when auto replace is off
 7557        editor.handle_input(" :wave", window, cx);
 7558        assert_eq!(
 7559            editor.text(cx),
 7560            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 7561        );
 7562
 7563        editor.handle_input(":", window, cx);
 7564        assert_eq!(
 7565            editor.text(cx),
 7566            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 7567        );
 7568    });
 7569}
 7570
 7571#[gpui::test]
 7572async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 7573    init_test(cx, |_| {});
 7574
 7575    let (text, insertion_ranges) = marked_text_ranges(
 7576        indoc! {"
 7577            ˇ
 7578        "},
 7579        false,
 7580    );
 7581
 7582    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7583    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7584
 7585    _ = editor.update_in(cx, |editor, window, cx| {
 7586        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 7587
 7588        editor
 7589            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7590            .unwrap();
 7591
 7592        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7593            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7594            assert_eq!(editor.text(cx), expected_text);
 7595            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7596        }
 7597
 7598        assert(
 7599            editor,
 7600            cx,
 7601            indoc! {"
 7602            type «» =•
 7603            "},
 7604        );
 7605
 7606        assert!(editor.context_menu_visible(), "There should be a matches");
 7607    });
 7608}
 7609
 7610#[gpui::test]
 7611async fn test_snippets(cx: &mut TestAppContext) {
 7612    init_test(cx, |_| {});
 7613
 7614    let (text, insertion_ranges) = marked_text_ranges(
 7615        indoc! {"
 7616            a.ˇ b
 7617            a.ˇ b
 7618            a.ˇ b
 7619        "},
 7620        false,
 7621    );
 7622
 7623    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7624    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7625
 7626    editor.update_in(cx, |editor, window, cx| {
 7627        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 7628
 7629        editor
 7630            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7631            .unwrap();
 7632
 7633        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7634            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7635            assert_eq!(editor.text(cx), expected_text);
 7636            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7637        }
 7638
 7639        assert(
 7640            editor,
 7641            cx,
 7642            indoc! {"
 7643                a.f(«one», two, «three») b
 7644                a.f(«one», two, «three») b
 7645                a.f(«one», two, «three») b
 7646            "},
 7647        );
 7648
 7649        // Can't move earlier than the first tab stop
 7650        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 7651        assert(
 7652            editor,
 7653            cx,
 7654            indoc! {"
 7655                a.f(«one», two, «three») b
 7656                a.f(«one», two, «three») b
 7657                a.f(«one», two, «three») b
 7658            "},
 7659        );
 7660
 7661        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7662        assert(
 7663            editor,
 7664            cx,
 7665            indoc! {"
 7666                a.f(one, «two», three) b
 7667                a.f(one, «two», three) b
 7668                a.f(one, «two», three) b
 7669            "},
 7670        );
 7671
 7672        editor.move_to_prev_snippet_tabstop(window, cx);
 7673        assert(
 7674            editor,
 7675            cx,
 7676            indoc! {"
 7677                a.f(«one», two, «three») b
 7678                a.f(«one», two, «three») b
 7679                a.f(«one», two, «three») b
 7680            "},
 7681        );
 7682
 7683        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7684        assert(
 7685            editor,
 7686            cx,
 7687            indoc! {"
 7688                a.f(one, «two», three) b
 7689                a.f(one, «two», three) b
 7690                a.f(one, «two», three) b
 7691            "},
 7692        );
 7693        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7694        assert(
 7695            editor,
 7696            cx,
 7697            indoc! {"
 7698                a.f(one, two, three)ˇ b
 7699                a.f(one, two, three)ˇ b
 7700                a.f(one, two, three)ˇ b
 7701            "},
 7702        );
 7703
 7704        // As soon as the last tab stop is reached, snippet state is gone
 7705        editor.move_to_prev_snippet_tabstop(window, cx);
 7706        assert(
 7707            editor,
 7708            cx,
 7709            indoc! {"
 7710                a.f(one, two, three)ˇ b
 7711                a.f(one, two, three)ˇ b
 7712                a.f(one, two, three)ˇ b
 7713            "},
 7714        );
 7715    });
 7716}
 7717
 7718#[gpui::test]
 7719async fn test_document_format_during_save(cx: &mut TestAppContext) {
 7720    init_test(cx, |_| {});
 7721
 7722    let fs = FakeFs::new(cx.executor());
 7723    fs.insert_file(path!("/file.rs"), Default::default()).await;
 7724
 7725    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 7726
 7727    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7728    language_registry.add(rust_lang());
 7729    let mut fake_servers = language_registry.register_fake_lsp(
 7730        "Rust",
 7731        FakeLspAdapter {
 7732            capabilities: lsp::ServerCapabilities {
 7733                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7734                ..Default::default()
 7735            },
 7736            ..Default::default()
 7737        },
 7738    );
 7739
 7740    let buffer = project
 7741        .update(cx, |project, cx| {
 7742            project.open_local_buffer(path!("/file.rs"), cx)
 7743        })
 7744        .await
 7745        .unwrap();
 7746
 7747    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7748    let (editor, cx) = cx.add_window_view(|window, cx| {
 7749        build_editor_with_project(project.clone(), buffer, window, cx)
 7750    });
 7751    editor.update_in(cx, |editor, window, cx| {
 7752        editor.set_text("one\ntwo\nthree\n", window, cx)
 7753    });
 7754    assert!(cx.read(|cx| editor.is_dirty(cx)));
 7755
 7756    cx.executor().start_waiting();
 7757    let fake_server = fake_servers.next().await.unwrap();
 7758
 7759    {
 7760        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 7761            move |params, _| async move {
 7762                assert_eq!(
 7763                    params.text_document.uri,
 7764                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7765                );
 7766                assert_eq!(params.options.tab_size, 4);
 7767                Ok(Some(vec![lsp::TextEdit::new(
 7768                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 7769                    ", ".to_string(),
 7770                )]))
 7771            },
 7772        );
 7773        let save = editor
 7774            .update_in(cx, |editor, window, cx| {
 7775                editor.save(true, project.clone(), window, cx)
 7776            })
 7777            .unwrap();
 7778        cx.executor().start_waiting();
 7779        save.await;
 7780
 7781        assert_eq!(
 7782            editor.update(cx, |editor, cx| editor.text(cx)),
 7783            "one, two\nthree\n"
 7784        );
 7785        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7786    }
 7787
 7788    {
 7789        editor.update_in(cx, |editor, window, cx| {
 7790            editor.set_text("one\ntwo\nthree\n", window, cx)
 7791        });
 7792        assert!(cx.read(|cx| editor.is_dirty(cx)));
 7793
 7794        // Ensure we can still save even if formatting hangs.
 7795        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 7796            move |params, _| async move {
 7797                assert_eq!(
 7798                    params.text_document.uri,
 7799                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7800                );
 7801                futures::future::pending::<()>().await;
 7802                unreachable!()
 7803            },
 7804        );
 7805        let save = editor
 7806            .update_in(cx, |editor, window, cx| {
 7807                editor.save(true, project.clone(), window, cx)
 7808            })
 7809            .unwrap();
 7810        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 7811        cx.executor().start_waiting();
 7812        save.await;
 7813        assert_eq!(
 7814            editor.update(cx, |editor, cx| editor.text(cx)),
 7815            "one\ntwo\nthree\n"
 7816        );
 7817    }
 7818
 7819    // For non-dirty buffer, no formatting request should be sent
 7820    {
 7821        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7822
 7823        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7824            panic!("Should not be invoked on non-dirty buffer");
 7825        });
 7826        let save = editor
 7827            .update_in(cx, |editor, window, cx| {
 7828                editor.save(true, project.clone(), window, cx)
 7829            })
 7830            .unwrap();
 7831        cx.executor().start_waiting();
 7832        save.await;
 7833    }
 7834
 7835    // Set rust language override and assert overridden tabsize is sent to language server
 7836    update_test_language_settings(cx, |settings| {
 7837        settings.languages.insert(
 7838            "Rust".into(),
 7839            LanguageSettingsContent {
 7840                tab_size: NonZeroU32::new(8),
 7841                ..Default::default()
 7842            },
 7843        );
 7844    });
 7845
 7846    {
 7847        editor.update_in(cx, |editor, window, cx| {
 7848            editor.set_text("somehting_new\n", window, cx)
 7849        });
 7850        assert!(cx.read(|cx| editor.is_dirty(cx)));
 7851        let _formatting_request_signal = fake_server
 7852            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7853                assert_eq!(
 7854                    params.text_document.uri,
 7855                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7856                );
 7857                assert_eq!(params.options.tab_size, 8);
 7858                Ok(Some(vec![]))
 7859            });
 7860        let save = editor
 7861            .update_in(cx, |editor, window, cx| {
 7862                editor.save(true, project.clone(), window, cx)
 7863            })
 7864            .unwrap();
 7865        cx.executor().start_waiting();
 7866        save.await;
 7867    }
 7868}
 7869
 7870#[gpui::test]
 7871async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 7872    init_test(cx, |_| {});
 7873
 7874    let cols = 4;
 7875    let rows = 10;
 7876    let sample_text_1 = sample_text(rows, cols, 'a');
 7877    assert_eq!(
 7878        sample_text_1,
 7879        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 7880    );
 7881    let sample_text_2 = sample_text(rows, cols, 'l');
 7882    assert_eq!(
 7883        sample_text_2,
 7884        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 7885    );
 7886    let sample_text_3 = sample_text(rows, cols, 'v');
 7887    assert_eq!(
 7888        sample_text_3,
 7889        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 7890    );
 7891
 7892    let fs = FakeFs::new(cx.executor());
 7893    fs.insert_tree(
 7894        path!("/a"),
 7895        json!({
 7896            "main.rs": sample_text_1,
 7897            "other.rs": sample_text_2,
 7898            "lib.rs": sample_text_3,
 7899        }),
 7900    )
 7901    .await;
 7902
 7903    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 7904    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7905    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 7906
 7907    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7908    language_registry.add(rust_lang());
 7909    let mut fake_servers = language_registry.register_fake_lsp(
 7910        "Rust",
 7911        FakeLspAdapter {
 7912            capabilities: lsp::ServerCapabilities {
 7913                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7914                ..Default::default()
 7915            },
 7916            ..Default::default()
 7917        },
 7918    );
 7919
 7920    let worktree = project.update(cx, |project, cx| {
 7921        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 7922        assert_eq!(worktrees.len(), 1);
 7923        worktrees.pop().unwrap()
 7924    });
 7925    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 7926
 7927    let buffer_1 = project
 7928        .update(cx, |project, cx| {
 7929            project.open_buffer((worktree_id, "main.rs"), cx)
 7930        })
 7931        .await
 7932        .unwrap();
 7933    let buffer_2 = project
 7934        .update(cx, |project, cx| {
 7935            project.open_buffer((worktree_id, "other.rs"), cx)
 7936        })
 7937        .await
 7938        .unwrap();
 7939    let buffer_3 = project
 7940        .update(cx, |project, cx| {
 7941            project.open_buffer((worktree_id, "lib.rs"), cx)
 7942        })
 7943        .await
 7944        .unwrap();
 7945
 7946    let multi_buffer = cx.new(|cx| {
 7947        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 7948        multi_buffer.push_excerpts(
 7949            buffer_1.clone(),
 7950            [
 7951                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 7952                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 7953                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 7954            ],
 7955            cx,
 7956        );
 7957        multi_buffer.push_excerpts(
 7958            buffer_2.clone(),
 7959            [
 7960                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 7961                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 7962                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 7963            ],
 7964            cx,
 7965        );
 7966        multi_buffer.push_excerpts(
 7967            buffer_3.clone(),
 7968            [
 7969                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 7970                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 7971                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 7972            ],
 7973            cx,
 7974        );
 7975        multi_buffer
 7976    });
 7977    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 7978        Editor::new(
 7979            EditorMode::Full,
 7980            multi_buffer,
 7981            Some(project.clone()),
 7982            window,
 7983            cx,
 7984        )
 7985    });
 7986
 7987    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 7988        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 7989            s.select_ranges(Some(1..2))
 7990        });
 7991        editor.insert("|one|two|three|", window, cx);
 7992    });
 7993    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 7994    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 7995        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 7996            s.select_ranges(Some(60..70))
 7997        });
 7998        editor.insert("|four|five|six|", window, cx);
 7999    });
 8000    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8001
 8002    // First two buffers should be edited, but not the third one.
 8003    assert_eq!(
 8004        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8005        "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}",
 8006    );
 8007    buffer_1.update(cx, |buffer, _| {
 8008        assert!(buffer.is_dirty());
 8009        assert_eq!(
 8010            buffer.text(),
 8011            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8012        )
 8013    });
 8014    buffer_2.update(cx, |buffer, _| {
 8015        assert!(buffer.is_dirty());
 8016        assert_eq!(
 8017            buffer.text(),
 8018            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8019        )
 8020    });
 8021    buffer_3.update(cx, |buffer, _| {
 8022        assert!(!buffer.is_dirty());
 8023        assert_eq!(buffer.text(), sample_text_3,)
 8024    });
 8025    cx.executor().run_until_parked();
 8026
 8027    cx.executor().start_waiting();
 8028    let save = multi_buffer_editor
 8029        .update_in(cx, |editor, window, cx| {
 8030            editor.save(true, project.clone(), window, cx)
 8031        })
 8032        .unwrap();
 8033
 8034    let fake_server = fake_servers.next().await.unwrap();
 8035    fake_server
 8036        .server
 8037        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8038            Ok(Some(vec![lsp::TextEdit::new(
 8039                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8040                format!("[{} formatted]", params.text_document.uri),
 8041            )]))
 8042        })
 8043        .detach();
 8044    save.await;
 8045
 8046    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8047    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8048    assert_eq!(
 8049        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8050        uri!(
 8051            "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}"
 8052        ),
 8053    );
 8054    buffer_1.update(cx, |buffer, _| {
 8055        assert!(!buffer.is_dirty());
 8056        assert_eq!(
 8057            buffer.text(),
 8058            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8059        )
 8060    });
 8061    buffer_2.update(cx, |buffer, _| {
 8062        assert!(!buffer.is_dirty());
 8063        assert_eq!(
 8064            buffer.text(),
 8065            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8066        )
 8067    });
 8068    buffer_3.update(cx, |buffer, _| {
 8069        assert!(!buffer.is_dirty());
 8070        assert_eq!(buffer.text(), sample_text_3,)
 8071    });
 8072}
 8073
 8074#[gpui::test]
 8075async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8076    init_test(cx, |_| {});
 8077
 8078    let fs = FakeFs::new(cx.executor());
 8079    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8080
 8081    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8082
 8083    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8084    language_registry.add(rust_lang());
 8085    let mut fake_servers = language_registry.register_fake_lsp(
 8086        "Rust",
 8087        FakeLspAdapter {
 8088            capabilities: lsp::ServerCapabilities {
 8089                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8090                ..Default::default()
 8091            },
 8092            ..Default::default()
 8093        },
 8094    );
 8095
 8096    let buffer = project
 8097        .update(cx, |project, cx| {
 8098            project.open_local_buffer(path!("/file.rs"), cx)
 8099        })
 8100        .await
 8101        .unwrap();
 8102
 8103    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8104    let (editor, cx) = cx.add_window_view(|window, cx| {
 8105        build_editor_with_project(project.clone(), buffer, window, cx)
 8106    });
 8107    editor.update_in(cx, |editor, window, cx| {
 8108        editor.set_text("one\ntwo\nthree\n", window, cx)
 8109    });
 8110    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8111
 8112    cx.executor().start_waiting();
 8113    let fake_server = fake_servers.next().await.unwrap();
 8114
 8115    let save = editor
 8116        .update_in(cx, |editor, window, cx| {
 8117            editor.save(true, project.clone(), window, cx)
 8118        })
 8119        .unwrap();
 8120    fake_server
 8121        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8122            assert_eq!(
 8123                params.text_document.uri,
 8124                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8125            );
 8126            assert_eq!(params.options.tab_size, 4);
 8127            Ok(Some(vec![lsp::TextEdit::new(
 8128                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8129                ", ".to_string(),
 8130            )]))
 8131        })
 8132        .next()
 8133        .await;
 8134    cx.executor().start_waiting();
 8135    save.await;
 8136    assert_eq!(
 8137        editor.update(cx, |editor, cx| editor.text(cx)),
 8138        "one, two\nthree\n"
 8139    );
 8140    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8141
 8142    editor.update_in(cx, |editor, window, cx| {
 8143        editor.set_text("one\ntwo\nthree\n", window, cx)
 8144    });
 8145    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8146
 8147    // Ensure we can still save even if formatting hangs.
 8148    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8149        move |params, _| async move {
 8150            assert_eq!(
 8151                params.text_document.uri,
 8152                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8153            );
 8154            futures::future::pending::<()>().await;
 8155            unreachable!()
 8156        },
 8157    );
 8158    let save = editor
 8159        .update_in(cx, |editor, window, cx| {
 8160            editor.save(true, project.clone(), window, cx)
 8161        })
 8162        .unwrap();
 8163    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8164    cx.executor().start_waiting();
 8165    save.await;
 8166    assert_eq!(
 8167        editor.update(cx, |editor, cx| editor.text(cx)),
 8168        "one\ntwo\nthree\n"
 8169    );
 8170    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8171
 8172    // For non-dirty buffer, no formatting request should be sent
 8173    let save = editor
 8174        .update_in(cx, |editor, window, cx| {
 8175            editor.save(true, project.clone(), window, cx)
 8176        })
 8177        .unwrap();
 8178    let _pending_format_request = fake_server
 8179        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8180            panic!("Should not be invoked on non-dirty buffer");
 8181        })
 8182        .next();
 8183    cx.executor().start_waiting();
 8184    save.await;
 8185
 8186    // Set Rust language override and assert overridden tabsize is sent to language server
 8187    update_test_language_settings(cx, |settings| {
 8188        settings.languages.insert(
 8189            "Rust".into(),
 8190            LanguageSettingsContent {
 8191                tab_size: NonZeroU32::new(8),
 8192                ..Default::default()
 8193            },
 8194        );
 8195    });
 8196
 8197    editor.update_in(cx, |editor, window, cx| {
 8198        editor.set_text("somehting_new\n", window, cx)
 8199    });
 8200    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8201    let save = editor
 8202        .update_in(cx, |editor, window, cx| {
 8203            editor.save(true, project.clone(), window, cx)
 8204        })
 8205        .unwrap();
 8206    fake_server
 8207        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8208            assert_eq!(
 8209                params.text_document.uri,
 8210                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8211            );
 8212            assert_eq!(params.options.tab_size, 8);
 8213            Ok(Some(vec![]))
 8214        })
 8215        .next()
 8216        .await;
 8217    cx.executor().start_waiting();
 8218    save.await;
 8219}
 8220
 8221#[gpui::test]
 8222async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8223    init_test(cx, |settings| {
 8224        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8225            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8226        ))
 8227    });
 8228
 8229    let fs = FakeFs::new(cx.executor());
 8230    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8231
 8232    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8233
 8234    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8235    language_registry.add(Arc::new(Language::new(
 8236        LanguageConfig {
 8237            name: "Rust".into(),
 8238            matcher: LanguageMatcher {
 8239                path_suffixes: vec!["rs".to_string()],
 8240                ..Default::default()
 8241            },
 8242            ..LanguageConfig::default()
 8243        },
 8244        Some(tree_sitter_rust::LANGUAGE.into()),
 8245    )));
 8246    update_test_language_settings(cx, |settings| {
 8247        // Enable Prettier formatting for the same buffer, and ensure
 8248        // LSP is called instead of Prettier.
 8249        settings.defaults.prettier = Some(PrettierSettings {
 8250            allowed: true,
 8251            ..PrettierSettings::default()
 8252        });
 8253    });
 8254    let mut fake_servers = language_registry.register_fake_lsp(
 8255        "Rust",
 8256        FakeLspAdapter {
 8257            capabilities: lsp::ServerCapabilities {
 8258                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8259                ..Default::default()
 8260            },
 8261            ..Default::default()
 8262        },
 8263    );
 8264
 8265    let buffer = project
 8266        .update(cx, |project, cx| {
 8267            project.open_local_buffer(path!("/file.rs"), cx)
 8268        })
 8269        .await
 8270        .unwrap();
 8271
 8272    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8273    let (editor, cx) = cx.add_window_view(|window, cx| {
 8274        build_editor_with_project(project.clone(), buffer, window, cx)
 8275    });
 8276    editor.update_in(cx, |editor, window, cx| {
 8277        editor.set_text("one\ntwo\nthree\n", window, cx)
 8278    });
 8279
 8280    cx.executor().start_waiting();
 8281    let fake_server = fake_servers.next().await.unwrap();
 8282
 8283    let format = editor
 8284        .update_in(cx, |editor, window, cx| {
 8285            editor.perform_format(
 8286                project.clone(),
 8287                FormatTrigger::Manual,
 8288                FormatTarget::Buffers,
 8289                window,
 8290                cx,
 8291            )
 8292        })
 8293        .unwrap();
 8294    fake_server
 8295        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8296            assert_eq!(
 8297                params.text_document.uri,
 8298                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8299            );
 8300            assert_eq!(params.options.tab_size, 4);
 8301            Ok(Some(vec![lsp::TextEdit::new(
 8302                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8303                ", ".to_string(),
 8304            )]))
 8305        })
 8306        .next()
 8307        .await;
 8308    cx.executor().start_waiting();
 8309    format.await;
 8310    assert_eq!(
 8311        editor.update(cx, |editor, cx| editor.text(cx)),
 8312        "one, two\nthree\n"
 8313    );
 8314
 8315    editor.update_in(cx, |editor, window, cx| {
 8316        editor.set_text("one\ntwo\nthree\n", window, cx)
 8317    });
 8318    // Ensure we don't lock if formatting hangs.
 8319    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8320        move |params, _| async move {
 8321            assert_eq!(
 8322                params.text_document.uri,
 8323                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8324            );
 8325            futures::future::pending::<()>().await;
 8326            unreachable!()
 8327        },
 8328    );
 8329    let format = editor
 8330        .update_in(cx, |editor, window, cx| {
 8331            editor.perform_format(
 8332                project,
 8333                FormatTrigger::Manual,
 8334                FormatTarget::Buffers,
 8335                window,
 8336                cx,
 8337            )
 8338        })
 8339        .unwrap();
 8340    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8341    cx.executor().start_waiting();
 8342    format.await;
 8343    assert_eq!(
 8344        editor.update(cx, |editor, cx| editor.text(cx)),
 8345        "one\ntwo\nthree\n"
 8346    );
 8347}
 8348
 8349#[gpui::test]
 8350async fn test_multiple_formatters(cx: &mut TestAppContext) {
 8351    init_test(cx, |settings| {
 8352        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 8353        settings.defaults.formatter =
 8354            Some(language_settings::SelectedFormatter::List(FormatterList(
 8355                vec![
 8356                    Formatter::LanguageServer { name: None },
 8357                    Formatter::CodeActions(
 8358                        [
 8359                            ("code-action-1".into(), true),
 8360                            ("code-action-2".into(), true),
 8361                        ]
 8362                        .into_iter()
 8363                        .collect(),
 8364                    ),
 8365                ]
 8366                .into(),
 8367            )))
 8368    });
 8369
 8370    let fs = FakeFs::new(cx.executor());
 8371    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 8372        .await;
 8373
 8374    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8375    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8376    language_registry.add(rust_lang());
 8377
 8378    let mut fake_servers = language_registry.register_fake_lsp(
 8379        "Rust",
 8380        FakeLspAdapter {
 8381            capabilities: lsp::ServerCapabilities {
 8382                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8383                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 8384                    commands: vec!["the-command-for-code-action-1".into()],
 8385                    ..Default::default()
 8386                }),
 8387                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8388                ..Default::default()
 8389            },
 8390            ..Default::default()
 8391        },
 8392    );
 8393
 8394    let buffer = project
 8395        .update(cx, |project, cx| {
 8396            project.open_local_buffer(path!("/file.rs"), cx)
 8397        })
 8398        .await
 8399        .unwrap();
 8400
 8401    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8402    let (editor, cx) = cx.add_window_view(|window, cx| {
 8403        build_editor_with_project(project.clone(), buffer, window, cx)
 8404    });
 8405
 8406    cx.executor().start_waiting();
 8407
 8408    let fake_server = fake_servers.next().await.unwrap();
 8409    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8410        move |_params, _| async move {
 8411            Ok(Some(vec![lsp::TextEdit::new(
 8412                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8413                "applied-formatting\n".to_string(),
 8414            )]))
 8415        },
 8416    );
 8417    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8418        move |params, _| async move {
 8419            assert_eq!(
 8420                params.context.only,
 8421                Some(vec!["code-action-1".into(), "code-action-2".into()])
 8422            );
 8423            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 8424            Ok(Some(vec![
 8425                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8426                    kind: Some("code-action-1".into()),
 8427                    edit: Some(lsp::WorkspaceEdit::new(
 8428                        [(
 8429                            uri.clone(),
 8430                            vec![lsp::TextEdit::new(
 8431                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8432                                "applied-code-action-1-edit\n".to_string(),
 8433                            )],
 8434                        )]
 8435                        .into_iter()
 8436                        .collect(),
 8437                    )),
 8438                    command: Some(lsp::Command {
 8439                        command: "the-command-for-code-action-1".into(),
 8440                        ..Default::default()
 8441                    }),
 8442                    ..Default::default()
 8443                }),
 8444                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8445                    kind: Some("code-action-2".into()),
 8446                    edit: Some(lsp::WorkspaceEdit::new(
 8447                        [(
 8448                            uri.clone(),
 8449                            vec![lsp::TextEdit::new(
 8450                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8451                                "applied-code-action-2-edit\n".to_string(),
 8452                            )],
 8453                        )]
 8454                        .into_iter()
 8455                        .collect(),
 8456                    )),
 8457                    ..Default::default()
 8458                }),
 8459            ]))
 8460        },
 8461    );
 8462
 8463    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 8464        move |params, _| async move { Ok(params) }
 8465    });
 8466
 8467    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 8468    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 8469        let fake = fake_server.clone();
 8470        let lock = command_lock.clone();
 8471        move |params, _| {
 8472            assert_eq!(params.command, "the-command-for-code-action-1");
 8473            let fake = fake.clone();
 8474            let lock = lock.clone();
 8475            async move {
 8476                lock.lock().await;
 8477                fake.server
 8478                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 8479                        label: None,
 8480                        edit: lsp::WorkspaceEdit {
 8481                            changes: Some(
 8482                                [(
 8483                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 8484                                    vec![lsp::TextEdit {
 8485                                        range: lsp::Range::new(
 8486                                            lsp::Position::new(0, 0),
 8487                                            lsp::Position::new(0, 0),
 8488                                        ),
 8489                                        new_text: "applied-code-action-1-command\n".into(),
 8490                                    }],
 8491                                )]
 8492                                .into_iter()
 8493                                .collect(),
 8494                            ),
 8495                            ..Default::default()
 8496                        },
 8497                    })
 8498                    .await
 8499                    .unwrap();
 8500                Ok(Some(json!(null)))
 8501            }
 8502        }
 8503    });
 8504
 8505    cx.executor().start_waiting();
 8506    editor
 8507        .update_in(cx, |editor, window, cx| {
 8508            editor.perform_format(
 8509                project.clone(),
 8510                FormatTrigger::Manual,
 8511                FormatTarget::Buffers,
 8512                window,
 8513                cx,
 8514            )
 8515        })
 8516        .unwrap()
 8517        .await;
 8518    editor.update(cx, |editor, cx| {
 8519        assert_eq!(
 8520            editor.text(cx),
 8521            r#"
 8522                applied-code-action-2-edit
 8523                applied-code-action-1-command
 8524                applied-code-action-1-edit
 8525                applied-formatting
 8526                one
 8527                two
 8528                three
 8529            "#
 8530            .unindent()
 8531        );
 8532    });
 8533
 8534    editor.update_in(cx, |editor, window, cx| {
 8535        editor.undo(&Default::default(), window, cx);
 8536        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8537    });
 8538
 8539    // Perform a manual edit while waiting for an LSP command
 8540    // that's being run as part of a formatting code action.
 8541    let lock_guard = command_lock.lock().await;
 8542    let format = editor
 8543        .update_in(cx, |editor, window, cx| {
 8544            editor.perform_format(
 8545                project.clone(),
 8546                FormatTrigger::Manual,
 8547                FormatTarget::Buffers,
 8548                window,
 8549                cx,
 8550            )
 8551        })
 8552        .unwrap();
 8553    cx.run_until_parked();
 8554    editor.update(cx, |editor, cx| {
 8555        assert_eq!(
 8556            editor.text(cx),
 8557            r#"
 8558                applied-code-action-1-edit
 8559                applied-formatting
 8560                one
 8561                two
 8562                three
 8563            "#
 8564            .unindent()
 8565        );
 8566
 8567        editor.buffer.update(cx, |buffer, cx| {
 8568            let ix = buffer.len(cx);
 8569            buffer.edit([(ix..ix, "edited\n")], None, cx);
 8570        });
 8571    });
 8572
 8573    // Allow the LSP command to proceed. Because the buffer was edited,
 8574    // the second code action will not be run.
 8575    drop(lock_guard);
 8576    format.await;
 8577    editor.update_in(cx, |editor, window, cx| {
 8578        assert_eq!(
 8579            editor.text(cx),
 8580            r#"
 8581                applied-code-action-1-command
 8582                applied-code-action-1-edit
 8583                applied-formatting
 8584                one
 8585                two
 8586                three
 8587                edited
 8588            "#
 8589            .unindent()
 8590        );
 8591
 8592        // The manual edit is undone first, because it is the last thing the user did
 8593        // (even though the command completed afterwards).
 8594        editor.undo(&Default::default(), window, cx);
 8595        assert_eq!(
 8596            editor.text(cx),
 8597            r#"
 8598                applied-code-action-1-command
 8599                applied-code-action-1-edit
 8600                applied-formatting
 8601                one
 8602                two
 8603                three
 8604            "#
 8605            .unindent()
 8606        );
 8607
 8608        // All the formatting (including the command, which completed after the manual edit)
 8609        // is undone together.
 8610        editor.undo(&Default::default(), window, cx);
 8611        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8612    });
 8613}
 8614
 8615#[gpui::test]
 8616async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 8617    init_test(cx, |settings| {
 8618        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8619            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8620        ))
 8621    });
 8622
 8623    let fs = FakeFs::new(cx.executor());
 8624    fs.insert_file(path!("/file.ts"), Default::default()).await;
 8625
 8626    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8627
 8628    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8629    language_registry.add(Arc::new(Language::new(
 8630        LanguageConfig {
 8631            name: "TypeScript".into(),
 8632            matcher: LanguageMatcher {
 8633                path_suffixes: vec!["ts".to_string()],
 8634                ..Default::default()
 8635            },
 8636            ..LanguageConfig::default()
 8637        },
 8638        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 8639    )));
 8640    update_test_language_settings(cx, |settings| {
 8641        settings.defaults.prettier = Some(PrettierSettings {
 8642            allowed: true,
 8643            ..PrettierSettings::default()
 8644        });
 8645    });
 8646    let mut fake_servers = language_registry.register_fake_lsp(
 8647        "TypeScript",
 8648        FakeLspAdapter {
 8649            capabilities: lsp::ServerCapabilities {
 8650                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8651                ..Default::default()
 8652            },
 8653            ..Default::default()
 8654        },
 8655    );
 8656
 8657    let buffer = project
 8658        .update(cx, |project, cx| {
 8659            project.open_local_buffer(path!("/file.ts"), cx)
 8660        })
 8661        .await
 8662        .unwrap();
 8663
 8664    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8665    let (editor, cx) = cx.add_window_view(|window, cx| {
 8666        build_editor_with_project(project.clone(), buffer, window, cx)
 8667    });
 8668    editor.update_in(cx, |editor, window, cx| {
 8669        editor.set_text(
 8670            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8671            window,
 8672            cx,
 8673        )
 8674    });
 8675
 8676    cx.executor().start_waiting();
 8677    let fake_server = fake_servers.next().await.unwrap();
 8678
 8679    let format = editor
 8680        .update_in(cx, |editor, window, cx| {
 8681            editor.perform_code_action_kind(
 8682                project.clone(),
 8683                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8684                window,
 8685                cx,
 8686            )
 8687        })
 8688        .unwrap();
 8689    fake_server
 8690        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 8691            assert_eq!(
 8692                params.text_document.uri,
 8693                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8694            );
 8695            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 8696                lsp::CodeAction {
 8697                    title: "Organize Imports".to_string(),
 8698                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 8699                    edit: Some(lsp::WorkspaceEdit {
 8700                        changes: Some(
 8701                            [(
 8702                                params.text_document.uri.clone(),
 8703                                vec![lsp::TextEdit::new(
 8704                                    lsp::Range::new(
 8705                                        lsp::Position::new(1, 0),
 8706                                        lsp::Position::new(2, 0),
 8707                                    ),
 8708                                    "".to_string(),
 8709                                )],
 8710                            )]
 8711                            .into_iter()
 8712                            .collect(),
 8713                        ),
 8714                        ..Default::default()
 8715                    }),
 8716                    ..Default::default()
 8717                },
 8718            )]))
 8719        })
 8720        .next()
 8721        .await;
 8722    cx.executor().start_waiting();
 8723    format.await;
 8724    assert_eq!(
 8725        editor.update(cx, |editor, cx| editor.text(cx)),
 8726        "import { a } from 'module';\n\nconst x = a;\n"
 8727    );
 8728
 8729    editor.update_in(cx, |editor, window, cx| {
 8730        editor.set_text(
 8731            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8732            window,
 8733            cx,
 8734        )
 8735    });
 8736    // Ensure we don't lock if code action hangs.
 8737    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8738        move |params, _| async move {
 8739            assert_eq!(
 8740                params.text_document.uri,
 8741                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8742            );
 8743            futures::future::pending::<()>().await;
 8744            unreachable!()
 8745        },
 8746    );
 8747    let format = editor
 8748        .update_in(cx, |editor, window, cx| {
 8749            editor.perform_code_action_kind(
 8750                project,
 8751                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8752                window,
 8753                cx,
 8754            )
 8755        })
 8756        .unwrap();
 8757    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 8758    cx.executor().start_waiting();
 8759    format.await;
 8760    assert_eq!(
 8761        editor.update(cx, |editor, cx| editor.text(cx)),
 8762        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 8763    );
 8764}
 8765
 8766#[gpui::test]
 8767async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 8768    init_test(cx, |_| {});
 8769
 8770    let mut cx = EditorLspTestContext::new_rust(
 8771        lsp::ServerCapabilities {
 8772            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8773            ..Default::default()
 8774        },
 8775        cx,
 8776    )
 8777    .await;
 8778
 8779    cx.set_state(indoc! {"
 8780        one.twoˇ
 8781    "});
 8782
 8783    // The format request takes a long time. When it completes, it inserts
 8784    // a newline and an indent before the `.`
 8785    cx.lsp
 8786        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 8787            let executor = cx.background_executor().clone();
 8788            async move {
 8789                executor.timer(Duration::from_millis(100)).await;
 8790                Ok(Some(vec![lsp::TextEdit {
 8791                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 8792                    new_text: "\n    ".into(),
 8793                }]))
 8794            }
 8795        });
 8796
 8797    // Submit a format request.
 8798    let format_1 = cx
 8799        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8800        .unwrap();
 8801    cx.executor().run_until_parked();
 8802
 8803    // Submit a second format request.
 8804    let format_2 = cx
 8805        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8806        .unwrap();
 8807    cx.executor().run_until_parked();
 8808
 8809    // Wait for both format requests to complete
 8810    cx.executor().advance_clock(Duration::from_millis(200));
 8811    cx.executor().start_waiting();
 8812    format_1.await.unwrap();
 8813    cx.executor().start_waiting();
 8814    format_2.await.unwrap();
 8815
 8816    // The formatting edits only happens once.
 8817    cx.assert_editor_state(indoc! {"
 8818        one
 8819            .twoˇ
 8820    "});
 8821}
 8822
 8823#[gpui::test]
 8824async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 8825    init_test(cx, |settings| {
 8826        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 8827    });
 8828
 8829    let mut cx = EditorLspTestContext::new_rust(
 8830        lsp::ServerCapabilities {
 8831            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8832            ..Default::default()
 8833        },
 8834        cx,
 8835    )
 8836    .await;
 8837
 8838    // Set up a buffer white some trailing whitespace and no trailing newline.
 8839    cx.set_state(
 8840        &[
 8841            "one ",   //
 8842            "twoˇ",   //
 8843            "three ", //
 8844            "four",   //
 8845        ]
 8846        .join("\n"),
 8847    );
 8848
 8849    // Submit a format request.
 8850    let format = cx
 8851        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8852        .unwrap();
 8853
 8854    // Record which buffer changes have been sent to the language server
 8855    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 8856    cx.lsp
 8857        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 8858            let buffer_changes = buffer_changes.clone();
 8859            move |params, _| {
 8860                buffer_changes.lock().extend(
 8861                    params
 8862                        .content_changes
 8863                        .into_iter()
 8864                        .map(|e| (e.range.unwrap(), e.text)),
 8865                );
 8866            }
 8867        });
 8868
 8869    // Handle formatting requests to the language server.
 8870    cx.lsp
 8871        .set_request_handler::<lsp::request::Formatting, _, _>({
 8872            let buffer_changes = buffer_changes.clone();
 8873            move |_, _| {
 8874                // When formatting is requested, trailing whitespace has already been stripped,
 8875                // and the trailing newline has already been added.
 8876                assert_eq!(
 8877                    &buffer_changes.lock()[1..],
 8878                    &[
 8879                        (
 8880                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 8881                            "".into()
 8882                        ),
 8883                        (
 8884                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 8885                            "".into()
 8886                        ),
 8887                        (
 8888                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 8889                            "\n".into()
 8890                        ),
 8891                    ]
 8892                );
 8893
 8894                // Insert blank lines between each line of the buffer.
 8895                async move {
 8896                    Ok(Some(vec![
 8897                        lsp::TextEdit {
 8898                            range: lsp::Range::new(
 8899                                lsp::Position::new(1, 0),
 8900                                lsp::Position::new(1, 0),
 8901                            ),
 8902                            new_text: "\n".into(),
 8903                        },
 8904                        lsp::TextEdit {
 8905                            range: lsp::Range::new(
 8906                                lsp::Position::new(2, 0),
 8907                                lsp::Position::new(2, 0),
 8908                            ),
 8909                            new_text: "\n".into(),
 8910                        },
 8911                    ]))
 8912                }
 8913            }
 8914        });
 8915
 8916    // After formatting the buffer, the trailing whitespace is stripped,
 8917    // a newline is appended, and the edits provided by the language server
 8918    // have been applied.
 8919    format.await.unwrap();
 8920    cx.assert_editor_state(
 8921        &[
 8922            "one",   //
 8923            "",      //
 8924            "twoˇ",  //
 8925            "",      //
 8926            "three", //
 8927            "four",  //
 8928            "",      //
 8929        ]
 8930        .join("\n"),
 8931    );
 8932
 8933    // Undoing the formatting undoes the trailing whitespace removal, the
 8934    // trailing newline, and the LSP edits.
 8935    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 8936    cx.assert_editor_state(
 8937        &[
 8938            "one ",   //
 8939            "twoˇ",   //
 8940            "three ", //
 8941            "four",   //
 8942        ]
 8943        .join("\n"),
 8944    );
 8945}
 8946
 8947#[gpui::test]
 8948async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 8949    cx: &mut TestAppContext,
 8950) {
 8951    init_test(cx, |_| {});
 8952
 8953    cx.update(|cx| {
 8954        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8955            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8956                settings.auto_signature_help = Some(true);
 8957            });
 8958        });
 8959    });
 8960
 8961    let mut cx = EditorLspTestContext::new_rust(
 8962        lsp::ServerCapabilities {
 8963            signature_help_provider: Some(lsp::SignatureHelpOptions {
 8964                ..Default::default()
 8965            }),
 8966            ..Default::default()
 8967        },
 8968        cx,
 8969    )
 8970    .await;
 8971
 8972    let language = Language::new(
 8973        LanguageConfig {
 8974            name: "Rust".into(),
 8975            brackets: BracketPairConfig {
 8976                pairs: vec![
 8977                    BracketPair {
 8978                        start: "{".to_string(),
 8979                        end: "}".to_string(),
 8980                        close: true,
 8981                        surround: true,
 8982                        newline: true,
 8983                    },
 8984                    BracketPair {
 8985                        start: "(".to_string(),
 8986                        end: ")".to_string(),
 8987                        close: true,
 8988                        surround: true,
 8989                        newline: true,
 8990                    },
 8991                    BracketPair {
 8992                        start: "/*".to_string(),
 8993                        end: " */".to_string(),
 8994                        close: true,
 8995                        surround: true,
 8996                        newline: true,
 8997                    },
 8998                    BracketPair {
 8999                        start: "[".to_string(),
 9000                        end: "]".to_string(),
 9001                        close: false,
 9002                        surround: false,
 9003                        newline: true,
 9004                    },
 9005                    BracketPair {
 9006                        start: "\"".to_string(),
 9007                        end: "\"".to_string(),
 9008                        close: true,
 9009                        surround: true,
 9010                        newline: false,
 9011                    },
 9012                    BracketPair {
 9013                        start: "<".to_string(),
 9014                        end: ">".to_string(),
 9015                        close: false,
 9016                        surround: true,
 9017                        newline: true,
 9018                    },
 9019                ],
 9020                ..Default::default()
 9021            },
 9022            autoclose_before: "})]".to_string(),
 9023            ..Default::default()
 9024        },
 9025        Some(tree_sitter_rust::LANGUAGE.into()),
 9026    );
 9027    let language = Arc::new(language);
 9028
 9029    cx.language_registry().add(language.clone());
 9030    cx.update_buffer(|buffer, cx| {
 9031        buffer.set_language(Some(language), cx);
 9032    });
 9033
 9034    cx.set_state(
 9035        &r#"
 9036            fn main() {
 9037                sampleˇ
 9038            }
 9039        "#
 9040        .unindent(),
 9041    );
 9042
 9043    cx.update_editor(|editor, window, cx| {
 9044        editor.handle_input("(", window, cx);
 9045    });
 9046    cx.assert_editor_state(
 9047        &"
 9048            fn main() {
 9049                sample(ˇ)
 9050            }
 9051        "
 9052        .unindent(),
 9053    );
 9054
 9055    let mocked_response = lsp::SignatureHelp {
 9056        signatures: vec![lsp::SignatureInformation {
 9057            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9058            documentation: None,
 9059            parameters: Some(vec![
 9060                lsp::ParameterInformation {
 9061                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9062                    documentation: None,
 9063                },
 9064                lsp::ParameterInformation {
 9065                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9066                    documentation: None,
 9067                },
 9068            ]),
 9069            active_parameter: None,
 9070        }],
 9071        active_signature: Some(0),
 9072        active_parameter: Some(0),
 9073    };
 9074    handle_signature_help_request(&mut cx, mocked_response).await;
 9075
 9076    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9077        .await;
 9078
 9079    cx.editor(|editor, _, _| {
 9080        let signature_help_state = editor.signature_help_state.popover().cloned();
 9081        assert_eq!(
 9082            signature_help_state.unwrap().label,
 9083            "param1: u8, param2: u8"
 9084        );
 9085    });
 9086}
 9087
 9088#[gpui::test]
 9089async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9090    init_test(cx, |_| {});
 9091
 9092    cx.update(|cx| {
 9093        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9094            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9095                settings.auto_signature_help = Some(false);
 9096                settings.show_signature_help_after_edits = Some(false);
 9097            });
 9098        });
 9099    });
 9100
 9101    let mut cx = EditorLspTestContext::new_rust(
 9102        lsp::ServerCapabilities {
 9103            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9104                ..Default::default()
 9105            }),
 9106            ..Default::default()
 9107        },
 9108        cx,
 9109    )
 9110    .await;
 9111
 9112    let language = Language::new(
 9113        LanguageConfig {
 9114            name: "Rust".into(),
 9115            brackets: BracketPairConfig {
 9116                pairs: vec![
 9117                    BracketPair {
 9118                        start: "{".to_string(),
 9119                        end: "}".to_string(),
 9120                        close: true,
 9121                        surround: true,
 9122                        newline: true,
 9123                    },
 9124                    BracketPair {
 9125                        start: "(".to_string(),
 9126                        end: ")".to_string(),
 9127                        close: true,
 9128                        surround: true,
 9129                        newline: true,
 9130                    },
 9131                    BracketPair {
 9132                        start: "/*".to_string(),
 9133                        end: " */".to_string(),
 9134                        close: true,
 9135                        surround: true,
 9136                        newline: true,
 9137                    },
 9138                    BracketPair {
 9139                        start: "[".to_string(),
 9140                        end: "]".to_string(),
 9141                        close: false,
 9142                        surround: false,
 9143                        newline: true,
 9144                    },
 9145                    BracketPair {
 9146                        start: "\"".to_string(),
 9147                        end: "\"".to_string(),
 9148                        close: true,
 9149                        surround: true,
 9150                        newline: false,
 9151                    },
 9152                    BracketPair {
 9153                        start: "<".to_string(),
 9154                        end: ">".to_string(),
 9155                        close: false,
 9156                        surround: true,
 9157                        newline: true,
 9158                    },
 9159                ],
 9160                ..Default::default()
 9161            },
 9162            autoclose_before: "})]".to_string(),
 9163            ..Default::default()
 9164        },
 9165        Some(tree_sitter_rust::LANGUAGE.into()),
 9166    );
 9167    let language = Arc::new(language);
 9168
 9169    cx.language_registry().add(language.clone());
 9170    cx.update_buffer(|buffer, cx| {
 9171        buffer.set_language(Some(language), cx);
 9172    });
 9173
 9174    // Ensure that signature_help is not called when no signature help is enabled.
 9175    cx.set_state(
 9176        &r#"
 9177            fn main() {
 9178                sampleˇ
 9179            }
 9180        "#
 9181        .unindent(),
 9182    );
 9183    cx.update_editor(|editor, window, cx| {
 9184        editor.handle_input("(", window, cx);
 9185    });
 9186    cx.assert_editor_state(
 9187        &"
 9188            fn main() {
 9189                sample(ˇ)
 9190            }
 9191        "
 9192        .unindent(),
 9193    );
 9194    cx.editor(|editor, _, _| {
 9195        assert!(editor.signature_help_state.task().is_none());
 9196    });
 9197
 9198    let mocked_response = lsp::SignatureHelp {
 9199        signatures: vec![lsp::SignatureInformation {
 9200            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9201            documentation: None,
 9202            parameters: Some(vec![
 9203                lsp::ParameterInformation {
 9204                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9205                    documentation: None,
 9206                },
 9207                lsp::ParameterInformation {
 9208                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9209                    documentation: None,
 9210                },
 9211            ]),
 9212            active_parameter: None,
 9213        }],
 9214        active_signature: Some(0),
 9215        active_parameter: Some(0),
 9216    };
 9217
 9218    // Ensure that signature_help is called when enabled afte edits
 9219    cx.update(|_, cx| {
 9220        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9221            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9222                settings.auto_signature_help = Some(false);
 9223                settings.show_signature_help_after_edits = Some(true);
 9224            });
 9225        });
 9226    });
 9227    cx.set_state(
 9228        &r#"
 9229            fn main() {
 9230                sampleˇ
 9231            }
 9232        "#
 9233        .unindent(),
 9234    );
 9235    cx.update_editor(|editor, window, cx| {
 9236        editor.handle_input("(", window, cx);
 9237    });
 9238    cx.assert_editor_state(
 9239        &"
 9240            fn main() {
 9241                sample(ˇ)
 9242            }
 9243        "
 9244        .unindent(),
 9245    );
 9246    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9247    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9248        .await;
 9249    cx.update_editor(|editor, _, _| {
 9250        let signature_help_state = editor.signature_help_state.popover().cloned();
 9251        assert!(signature_help_state.is_some());
 9252        assert_eq!(
 9253            signature_help_state.unwrap().label,
 9254            "param1: u8, param2: u8"
 9255        );
 9256        editor.signature_help_state = SignatureHelpState::default();
 9257    });
 9258
 9259    // Ensure that signature_help is called when auto signature help override is enabled
 9260    cx.update(|_, cx| {
 9261        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9262            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9263                settings.auto_signature_help = Some(true);
 9264                settings.show_signature_help_after_edits = Some(false);
 9265            });
 9266        });
 9267    });
 9268    cx.set_state(
 9269        &r#"
 9270            fn main() {
 9271                sampleˇ
 9272            }
 9273        "#
 9274        .unindent(),
 9275    );
 9276    cx.update_editor(|editor, window, cx| {
 9277        editor.handle_input("(", window, cx);
 9278    });
 9279    cx.assert_editor_state(
 9280        &"
 9281            fn main() {
 9282                sample(ˇ)
 9283            }
 9284        "
 9285        .unindent(),
 9286    );
 9287    handle_signature_help_request(&mut cx, mocked_response).await;
 9288    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9289        .await;
 9290    cx.editor(|editor, _, _| {
 9291        let signature_help_state = editor.signature_help_state.popover().cloned();
 9292        assert!(signature_help_state.is_some());
 9293        assert_eq!(
 9294            signature_help_state.unwrap().label,
 9295            "param1: u8, param2: u8"
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_signature_help(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303    cx.update(|cx| {
 9304        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9305            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9306                settings.auto_signature_help = Some(true);
 9307            });
 9308        });
 9309    });
 9310
 9311    let mut cx = EditorLspTestContext::new_rust(
 9312        lsp::ServerCapabilities {
 9313            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9314                ..Default::default()
 9315            }),
 9316            ..Default::default()
 9317        },
 9318        cx,
 9319    )
 9320    .await;
 9321
 9322    // A test that directly calls `show_signature_help`
 9323    cx.update_editor(|editor, window, cx| {
 9324        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9325    });
 9326
 9327    let mocked_response = lsp::SignatureHelp {
 9328        signatures: vec![lsp::SignatureInformation {
 9329            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9330            documentation: None,
 9331            parameters: Some(vec![
 9332                lsp::ParameterInformation {
 9333                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9334                    documentation: None,
 9335                },
 9336                lsp::ParameterInformation {
 9337                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9338                    documentation: None,
 9339                },
 9340            ]),
 9341            active_parameter: None,
 9342        }],
 9343        active_signature: Some(0),
 9344        active_parameter: Some(0),
 9345    };
 9346    handle_signature_help_request(&mut cx, mocked_response).await;
 9347
 9348    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9349        .await;
 9350
 9351    cx.editor(|editor, _, _| {
 9352        let signature_help_state = editor.signature_help_state.popover().cloned();
 9353        assert!(signature_help_state.is_some());
 9354        assert_eq!(
 9355            signature_help_state.unwrap().label,
 9356            "param1: u8, param2: u8"
 9357        );
 9358    });
 9359
 9360    // When exiting outside from inside the brackets, `signature_help` is closed.
 9361    cx.set_state(indoc! {"
 9362        fn main() {
 9363            sample(ˇ);
 9364        }
 9365
 9366        fn sample(param1: u8, param2: u8) {}
 9367    "});
 9368
 9369    cx.update_editor(|editor, window, cx| {
 9370        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
 9371    });
 9372
 9373    let mocked_response = lsp::SignatureHelp {
 9374        signatures: Vec::new(),
 9375        active_signature: None,
 9376        active_parameter: None,
 9377    };
 9378    handle_signature_help_request(&mut cx, mocked_response).await;
 9379
 9380    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9381        .await;
 9382
 9383    cx.editor(|editor, _, _| {
 9384        assert!(!editor.signature_help_state.is_shown());
 9385    });
 9386
 9387    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
 9388    cx.set_state(indoc! {"
 9389        fn main() {
 9390            sample(ˇ);
 9391        }
 9392
 9393        fn sample(param1: u8, param2: u8) {}
 9394    "});
 9395
 9396    let mocked_response = lsp::SignatureHelp {
 9397        signatures: vec![lsp::SignatureInformation {
 9398            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9399            documentation: None,
 9400            parameters: Some(vec![
 9401                lsp::ParameterInformation {
 9402                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9403                    documentation: None,
 9404                },
 9405                lsp::ParameterInformation {
 9406                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9407                    documentation: None,
 9408                },
 9409            ]),
 9410            active_parameter: None,
 9411        }],
 9412        active_signature: Some(0),
 9413        active_parameter: Some(0),
 9414    };
 9415    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9416    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9417        .await;
 9418    cx.editor(|editor, _, _| {
 9419        assert!(editor.signature_help_state.is_shown());
 9420    });
 9421
 9422    // Restore the popover with more parameter input
 9423    cx.set_state(indoc! {"
 9424        fn main() {
 9425            sample(param1, param2ˇ);
 9426        }
 9427
 9428        fn sample(param1: u8, param2: u8) {}
 9429    "});
 9430
 9431    let mocked_response = lsp::SignatureHelp {
 9432        signatures: vec![lsp::SignatureInformation {
 9433            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9434            documentation: None,
 9435            parameters: Some(vec![
 9436                lsp::ParameterInformation {
 9437                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9438                    documentation: None,
 9439                },
 9440                lsp::ParameterInformation {
 9441                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9442                    documentation: None,
 9443                },
 9444            ]),
 9445            active_parameter: None,
 9446        }],
 9447        active_signature: Some(0),
 9448        active_parameter: Some(1),
 9449    };
 9450    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9451    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9452        .await;
 9453
 9454    // When selecting a range, the popover is gone.
 9455    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
 9456    cx.update_editor(|editor, window, cx| {
 9457        editor.change_selections(None, window, cx, |s| {
 9458            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9459        })
 9460    });
 9461    cx.assert_editor_state(indoc! {"
 9462        fn main() {
 9463            sample(param1, «ˇparam2»);
 9464        }
 9465
 9466        fn sample(param1: u8, param2: u8) {}
 9467    "});
 9468    cx.editor(|editor, _, _| {
 9469        assert!(!editor.signature_help_state.is_shown());
 9470    });
 9471
 9472    // When unselecting again, the popover is back if within the brackets.
 9473    cx.update_editor(|editor, window, cx| {
 9474        editor.change_selections(None, window, cx, |s| {
 9475            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9476        })
 9477    });
 9478    cx.assert_editor_state(indoc! {"
 9479        fn main() {
 9480            sample(param1, ˇparam2);
 9481        }
 9482
 9483        fn sample(param1: u8, param2: u8) {}
 9484    "});
 9485    handle_signature_help_request(&mut cx, mocked_response).await;
 9486    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9487        .await;
 9488    cx.editor(|editor, _, _| {
 9489        assert!(editor.signature_help_state.is_shown());
 9490    });
 9491
 9492    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
 9493    cx.update_editor(|editor, window, cx| {
 9494        editor.change_selections(None, window, cx, |s| {
 9495            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
 9496            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9497        })
 9498    });
 9499    cx.assert_editor_state(indoc! {"
 9500        fn main() {
 9501            sample(param1, ˇparam2);
 9502        }
 9503
 9504        fn sample(param1: u8, param2: u8) {}
 9505    "});
 9506
 9507    let mocked_response = lsp::SignatureHelp {
 9508        signatures: vec![lsp::SignatureInformation {
 9509            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9510            documentation: None,
 9511            parameters: Some(vec![
 9512                lsp::ParameterInformation {
 9513                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9514                    documentation: None,
 9515                },
 9516                lsp::ParameterInformation {
 9517                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9518                    documentation: None,
 9519                },
 9520            ]),
 9521            active_parameter: None,
 9522        }],
 9523        active_signature: Some(0),
 9524        active_parameter: Some(1),
 9525    };
 9526    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9527    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9528        .await;
 9529    cx.update_editor(|editor, _, cx| {
 9530        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
 9531    });
 9532    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9533        .await;
 9534    cx.update_editor(|editor, window, cx| {
 9535        editor.change_selections(None, window, cx, |s| {
 9536            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9537        })
 9538    });
 9539    cx.assert_editor_state(indoc! {"
 9540        fn main() {
 9541            sample(param1, «ˇparam2»);
 9542        }
 9543
 9544        fn sample(param1: u8, param2: u8) {}
 9545    "});
 9546    cx.update_editor(|editor, window, cx| {
 9547        editor.change_selections(None, window, cx, |s| {
 9548            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9549        })
 9550    });
 9551    cx.assert_editor_state(indoc! {"
 9552        fn main() {
 9553            sample(param1, ˇparam2);
 9554        }
 9555
 9556        fn sample(param1: u8, param2: u8) {}
 9557    "});
 9558    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
 9559        .await;
 9560}
 9561
 9562#[gpui::test]
 9563async fn test_completion_mode(cx: &mut TestAppContext) {
 9564    init_test(cx, |_| {});
 9565    let mut cx = EditorLspTestContext::new_rust(
 9566        lsp::ServerCapabilities {
 9567            completion_provider: Some(lsp::CompletionOptions {
 9568                resolve_provider: Some(true),
 9569                ..Default::default()
 9570            }),
 9571            ..Default::default()
 9572        },
 9573        cx,
 9574    )
 9575    .await;
 9576
 9577    struct Run {
 9578        run_description: &'static str,
 9579        initial_state: String,
 9580        buffer_marked_text: String,
 9581        completion_text: &'static str,
 9582        expected_with_insert_mode: String,
 9583        expected_with_replace_mode: String,
 9584        expected_with_replace_subsequence_mode: String,
 9585        expected_with_replace_suffix_mode: String,
 9586    }
 9587
 9588    let runs = [
 9589        Run {
 9590            run_description: "Start of word matches completion text",
 9591            initial_state: "before ediˇ after".into(),
 9592            buffer_marked_text: "before <edi|> after".into(),
 9593            completion_text: "editor",
 9594            expected_with_insert_mode: "before editorˇ after".into(),
 9595            expected_with_replace_mode: "before editorˇ after".into(),
 9596            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9597            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9598        },
 9599        Run {
 9600            run_description: "Accept same text at the middle of the word",
 9601            initial_state: "before ediˇtor after".into(),
 9602            buffer_marked_text: "before <edi|tor> after".into(),
 9603            completion_text: "editor",
 9604            expected_with_insert_mode: "before editorˇtor after".into(),
 9605            expected_with_replace_mode: "before ediˇtor after".into(),
 9606            expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
 9607            expected_with_replace_suffix_mode: "before ediˇtor after".into(),
 9608        },
 9609        Run {
 9610            run_description: "End of word matches completion text -- cursor at end",
 9611            initial_state: "before torˇ after".into(),
 9612            buffer_marked_text: "before <tor|> after".into(),
 9613            completion_text: "editor",
 9614            expected_with_insert_mode: "before editorˇ after".into(),
 9615            expected_with_replace_mode: "before editorˇ after".into(),
 9616            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9617            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9618        },
 9619        Run {
 9620            run_description: "End of word matches completion text -- cursor at start",
 9621            initial_state: "before ˇtor after".into(),
 9622            buffer_marked_text: "before <|tor> after".into(),
 9623            completion_text: "editor",
 9624            expected_with_insert_mode: "before editorˇtor after".into(),
 9625            expected_with_replace_mode: "before editorˇ after".into(),
 9626            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9627            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9628        },
 9629        Run {
 9630            run_description: "Prepend text containing whitespace",
 9631            initial_state: "pˇfield: bool".into(),
 9632            buffer_marked_text: "<p|field>: bool".into(),
 9633            completion_text: "pub ",
 9634            expected_with_insert_mode: "pub ˇfield: bool".into(),
 9635            expected_with_replace_mode: "pub ˇ: bool".into(),
 9636            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
 9637            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
 9638        },
 9639        Run {
 9640            run_description: "Add element to start of list",
 9641            initial_state: "[element_ˇelement_2]".into(),
 9642            buffer_marked_text: "[<element_|element_2>]".into(),
 9643            completion_text: "element_1",
 9644            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
 9645            expected_with_replace_mode: "[element_1ˇ]".into(),
 9646            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
 9647            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
 9648        },
 9649        Run {
 9650            run_description: "Add element to start of list -- first and second elements are equal",
 9651            initial_state: "[elˇelement]".into(),
 9652            buffer_marked_text: "[<el|element>]".into(),
 9653            completion_text: "element",
 9654            expected_with_insert_mode: "[elementˇelement]".into(),
 9655            expected_with_replace_mode: "[elˇement]".into(),
 9656            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
 9657            expected_with_replace_suffix_mode: "[elˇement]".into(),
 9658        },
 9659        Run {
 9660            run_description: "Ends with matching suffix",
 9661            initial_state: "SubˇError".into(),
 9662            buffer_marked_text: "<Sub|Error>".into(),
 9663            completion_text: "SubscriptionError",
 9664            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
 9665            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9666            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9667            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
 9668        },
 9669        Run {
 9670            run_description: "Suffix is a subsequence -- contiguous",
 9671            initial_state: "SubˇErr".into(),
 9672            buffer_marked_text: "<Sub|Err>".into(),
 9673            completion_text: "SubscriptionError",
 9674            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
 9675            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9676            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9677            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
 9678        },
 9679        Run {
 9680            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
 9681            initial_state: "Suˇscrirr".into(),
 9682            buffer_marked_text: "<Su|scrirr>".into(),
 9683            completion_text: "SubscriptionError",
 9684            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
 9685            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9686            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9687            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
 9688        },
 9689        Run {
 9690            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
 9691            initial_state: "foo(indˇix)".into(),
 9692            buffer_marked_text: "foo(<ind|ix>)".into(),
 9693            completion_text: "node_index",
 9694            expected_with_insert_mode: "foo(node_indexˇix)".into(),
 9695            expected_with_replace_mode: "foo(node_indexˇ)".into(),
 9696            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
 9697            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
 9698        },
 9699    ];
 9700
 9701    for run in runs {
 9702        let run_variations = [
 9703            (LspInsertMode::Insert, run.expected_with_insert_mode),
 9704            (LspInsertMode::Replace, run.expected_with_replace_mode),
 9705            (
 9706                LspInsertMode::ReplaceSubsequence,
 9707                run.expected_with_replace_subsequence_mode,
 9708            ),
 9709            (
 9710                LspInsertMode::ReplaceSuffix,
 9711                run.expected_with_replace_suffix_mode,
 9712            ),
 9713        ];
 9714
 9715        for (lsp_insert_mode, expected_text) in run_variations {
 9716            eprintln!(
 9717                "run = {:?}, mode = {lsp_insert_mode:.?}",
 9718                run.run_description,
 9719            );
 9720
 9721            update_test_language_settings(&mut cx, |settings| {
 9722                settings.defaults.completions = Some(CompletionSettings {
 9723                    lsp_insert_mode,
 9724                    words: WordsCompletionMode::Disabled,
 9725                    lsp: true,
 9726                    lsp_fetch_timeout_ms: 0,
 9727                });
 9728            });
 9729
 9730            cx.set_state(&run.initial_state);
 9731            cx.update_editor(|editor, window, cx| {
 9732                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9733            });
 9734
 9735            let counter = Arc::new(AtomicUsize::new(0));
 9736            handle_completion_request_with_insert_and_replace(
 9737                &mut cx,
 9738                &run.buffer_marked_text,
 9739                vec![run.completion_text],
 9740                counter.clone(),
 9741            )
 9742            .await;
 9743            cx.condition(|editor, _| editor.context_menu_visible())
 9744                .await;
 9745            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9746
 9747            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9748                editor
 9749                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9750                    .unwrap()
 9751            });
 9752            cx.assert_editor_state(&expected_text);
 9753            handle_resolve_completion_request(&mut cx, None).await;
 9754            apply_additional_edits.await.unwrap();
 9755        }
 9756    }
 9757}
 9758
 9759#[gpui::test]
 9760async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
 9761    init_test(cx, |_| {});
 9762    let mut cx = EditorLspTestContext::new_rust(
 9763        lsp::ServerCapabilities {
 9764            completion_provider: Some(lsp::CompletionOptions {
 9765                resolve_provider: Some(true),
 9766                ..Default::default()
 9767            }),
 9768            ..Default::default()
 9769        },
 9770        cx,
 9771    )
 9772    .await;
 9773
 9774    let initial_state = "SubˇError";
 9775    let buffer_marked_text = "<Sub|Error>";
 9776    let completion_text = "SubscriptionError";
 9777    let expected_with_insert_mode = "SubscriptionErrorˇError";
 9778    let expected_with_replace_mode = "SubscriptionErrorˇ";
 9779
 9780    update_test_language_settings(&mut cx, |settings| {
 9781        settings.defaults.completions = Some(CompletionSettings {
 9782            words: WordsCompletionMode::Disabled,
 9783            // set the opposite here to ensure that the action is overriding the default behavior
 9784            lsp_insert_mode: LspInsertMode::Insert,
 9785            lsp: true,
 9786            lsp_fetch_timeout_ms: 0,
 9787        });
 9788    });
 9789
 9790    cx.set_state(initial_state);
 9791    cx.update_editor(|editor, window, cx| {
 9792        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9793    });
 9794
 9795    let counter = Arc::new(AtomicUsize::new(0));
 9796    handle_completion_request_with_insert_and_replace(
 9797        &mut cx,
 9798        &buffer_marked_text,
 9799        vec![completion_text],
 9800        counter.clone(),
 9801    )
 9802    .await;
 9803    cx.condition(|editor, _| editor.context_menu_visible())
 9804        .await;
 9805    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9806
 9807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9808        editor
 9809            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
 9810            .unwrap()
 9811    });
 9812    cx.assert_editor_state(&expected_with_replace_mode);
 9813    handle_resolve_completion_request(&mut cx, None).await;
 9814    apply_additional_edits.await.unwrap();
 9815
 9816    update_test_language_settings(&mut cx, |settings| {
 9817        settings.defaults.completions = Some(CompletionSettings {
 9818            words: WordsCompletionMode::Disabled,
 9819            // set the opposite here to ensure that the action is overriding the default behavior
 9820            lsp_insert_mode: LspInsertMode::Replace,
 9821            lsp: true,
 9822            lsp_fetch_timeout_ms: 0,
 9823        });
 9824    });
 9825
 9826    cx.set_state(initial_state);
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9829    });
 9830    handle_completion_request_with_insert_and_replace(
 9831        &mut cx,
 9832        &buffer_marked_text,
 9833        vec![completion_text],
 9834        counter.clone(),
 9835    )
 9836    .await;
 9837    cx.condition(|editor, _| editor.context_menu_visible())
 9838        .await;
 9839    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
 9840
 9841    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9842        editor
 9843            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
 9844            .unwrap()
 9845    });
 9846    cx.assert_editor_state(&expected_with_insert_mode);
 9847    handle_resolve_completion_request(&mut cx, None).await;
 9848    apply_additional_edits.await.unwrap();
 9849}
 9850
 9851#[gpui::test]
 9852async fn test_completion(cx: &mut TestAppContext) {
 9853    init_test(cx, |_| {});
 9854
 9855    let mut cx = EditorLspTestContext::new_rust(
 9856        lsp::ServerCapabilities {
 9857            completion_provider: Some(lsp::CompletionOptions {
 9858                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9859                resolve_provider: Some(true),
 9860                ..Default::default()
 9861            }),
 9862            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9863            ..Default::default()
 9864        },
 9865        cx,
 9866    )
 9867    .await;
 9868    let counter = Arc::new(AtomicUsize::new(0));
 9869
 9870    cx.set_state(indoc! {"
 9871        oneˇ
 9872        two
 9873        three
 9874    "});
 9875    cx.simulate_keystroke(".");
 9876    handle_completion_request(
 9877        &mut cx,
 9878        indoc! {"
 9879            one.|<>
 9880            two
 9881            three
 9882        "},
 9883        vec!["first_completion", "second_completion"],
 9884        counter.clone(),
 9885    )
 9886    .await;
 9887    cx.condition(|editor, _| editor.context_menu_visible())
 9888        .await;
 9889    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9890
 9891    let _handler = handle_signature_help_request(
 9892        &mut cx,
 9893        lsp::SignatureHelp {
 9894            signatures: vec![lsp::SignatureInformation {
 9895                label: "test signature".to_string(),
 9896                documentation: None,
 9897                parameters: Some(vec![lsp::ParameterInformation {
 9898                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
 9899                    documentation: None,
 9900                }]),
 9901                active_parameter: None,
 9902            }],
 9903            active_signature: None,
 9904            active_parameter: None,
 9905        },
 9906    );
 9907    cx.update_editor(|editor, window, cx| {
 9908        assert!(
 9909            !editor.signature_help_state.is_shown(),
 9910            "No signature help was called for"
 9911        );
 9912        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9913    });
 9914    cx.run_until_parked();
 9915    cx.update_editor(|editor, _, _| {
 9916        assert!(
 9917            !editor.signature_help_state.is_shown(),
 9918            "No signature help should be shown when completions menu is open"
 9919        );
 9920    });
 9921
 9922    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9923        editor.context_menu_next(&Default::default(), window, cx);
 9924        editor
 9925            .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9926            .unwrap()
 9927    });
 9928    cx.assert_editor_state(indoc! {"
 9929        one.second_completionˇ
 9930        two
 9931        three
 9932    "});
 9933
 9934    handle_resolve_completion_request(
 9935        &mut cx,
 9936        Some(vec![
 9937            (
 9938                //This overlaps with the primary completion edit which is
 9939                //misbehavior from the LSP spec, test that we filter it out
 9940                indoc! {"
 9941                    one.second_ˇcompletion
 9942                    two
 9943                    threeˇ
 9944                "},
 9945                "overlapping additional edit",
 9946            ),
 9947            (
 9948                indoc! {"
 9949                    one.second_completion
 9950                    two
 9951                    threeˇ
 9952                "},
 9953                "\nadditional edit",
 9954            ),
 9955        ]),
 9956    )
 9957    .await;
 9958    apply_additional_edits.await.unwrap();
 9959    cx.assert_editor_state(indoc! {"
 9960        one.second_completionˇ
 9961        two
 9962        three
 9963        additional edit
 9964    "});
 9965
 9966    cx.set_state(indoc! {"
 9967        one.second_completion
 9968        twoˇ
 9969        threeˇ
 9970        additional edit
 9971    "});
 9972    cx.simulate_keystroke(" ");
 9973    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9974    cx.simulate_keystroke("s");
 9975    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9976
 9977    cx.assert_editor_state(indoc! {"
 9978        one.second_completion
 9979        two sˇ
 9980        three sˇ
 9981        additional edit
 9982    "});
 9983    handle_completion_request(
 9984        &mut cx,
 9985        indoc! {"
 9986            one.second_completion
 9987            two s
 9988            three <s|>
 9989            additional edit
 9990        "},
 9991        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 9992        counter.clone(),
 9993    )
 9994    .await;
 9995    cx.condition(|editor, _| editor.context_menu_visible())
 9996        .await;
 9997    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
 9998
 9999    cx.simulate_keystroke("i");
10000
10001    handle_completion_request(
10002        &mut cx,
10003        indoc! {"
10004            one.second_completion
10005            two si
10006            three <si|>
10007            additional edit
10008        "},
10009        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10010        counter.clone(),
10011    )
10012    .await;
10013    cx.condition(|editor, _| editor.context_menu_visible())
10014        .await;
10015    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10016
10017    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10018        editor
10019            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10020            .unwrap()
10021    });
10022    cx.assert_editor_state(indoc! {"
10023        one.second_completion
10024        two sixth_completionˇ
10025        three sixth_completionˇ
10026        additional edit
10027    "});
10028
10029    apply_additional_edits.await.unwrap();
10030
10031    update_test_language_settings(&mut cx, |settings| {
10032        settings.defaults.show_completions_on_input = Some(false);
10033    });
10034    cx.set_state("editorˇ");
10035    cx.simulate_keystroke(".");
10036    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10037    cx.simulate_keystrokes("c l o");
10038    cx.assert_editor_state("editor.cloˇ");
10039    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10040    cx.update_editor(|editor, window, cx| {
10041        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10042    });
10043    handle_completion_request(
10044        &mut cx,
10045        "editor.<clo|>",
10046        vec!["close", "clobber"],
10047        counter.clone(),
10048    )
10049    .await;
10050    cx.condition(|editor, _| editor.context_menu_visible())
10051        .await;
10052    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10053
10054    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10055        editor
10056            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10057            .unwrap()
10058    });
10059    cx.assert_editor_state("editor.closeˇ");
10060    handle_resolve_completion_request(&mut cx, None).await;
10061    apply_additional_edits.await.unwrap();
10062}
10063
10064#[gpui::test]
10065async fn test_word_completion(cx: &mut TestAppContext) {
10066    let lsp_fetch_timeout_ms = 10;
10067    init_test(cx, |language_settings| {
10068        language_settings.defaults.completions = Some(CompletionSettings {
10069            words: WordsCompletionMode::Fallback,
10070            lsp: true,
10071            lsp_fetch_timeout_ms: 10,
10072            lsp_insert_mode: LspInsertMode::Insert,
10073        });
10074    });
10075
10076    let mut cx = EditorLspTestContext::new_rust(
10077        lsp::ServerCapabilities {
10078            completion_provider: Some(lsp::CompletionOptions {
10079                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10080                ..lsp::CompletionOptions::default()
10081            }),
10082            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10083            ..lsp::ServerCapabilities::default()
10084        },
10085        cx,
10086    )
10087    .await;
10088
10089    let throttle_completions = Arc::new(AtomicBool::new(false));
10090
10091    let lsp_throttle_completions = throttle_completions.clone();
10092    let _completion_requests_handler =
10093        cx.lsp
10094            .server
10095            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10096                let lsp_throttle_completions = lsp_throttle_completions.clone();
10097                let cx = cx.clone();
10098                async move {
10099                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10100                        cx.background_executor()
10101                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10102                            .await;
10103                    }
10104                    Ok(Some(lsp::CompletionResponse::Array(vec![
10105                        lsp::CompletionItem {
10106                            label: "first".into(),
10107                            ..lsp::CompletionItem::default()
10108                        },
10109                        lsp::CompletionItem {
10110                            label: "last".into(),
10111                            ..lsp::CompletionItem::default()
10112                        },
10113                    ])))
10114                }
10115            });
10116
10117    cx.set_state(indoc! {"
10118        oneˇ
10119        two
10120        three
10121    "});
10122    cx.simulate_keystroke(".");
10123    cx.executor().run_until_parked();
10124    cx.condition(|editor, _| editor.context_menu_visible())
10125        .await;
10126    cx.update_editor(|editor, window, cx| {
10127        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10128        {
10129            assert_eq!(
10130                completion_menu_entries(&menu),
10131                &["first", "last"],
10132                "When LSP server is fast to reply, no fallback word completions are used"
10133            );
10134        } else {
10135            panic!("expected completion menu to be open");
10136        }
10137        editor.cancel(&Cancel, window, cx);
10138    });
10139    cx.executor().run_until_parked();
10140    cx.condition(|editor, _| !editor.context_menu_visible())
10141        .await;
10142
10143    throttle_completions.store(true, atomic::Ordering::Release);
10144    cx.simulate_keystroke(".");
10145    cx.executor()
10146        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10147    cx.executor().run_until_parked();
10148    cx.condition(|editor, _| editor.context_menu_visible())
10149        .await;
10150    cx.update_editor(|editor, _, _| {
10151        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10152        {
10153            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10154                "When LSP server is slow, document words can be shown instead, if configured accordingly");
10155        } else {
10156            panic!("expected completion menu to be open");
10157        }
10158    });
10159}
10160
10161#[gpui::test]
10162async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10163    init_test(cx, |language_settings| {
10164        language_settings.defaults.completions = Some(CompletionSettings {
10165            words: WordsCompletionMode::Enabled,
10166            lsp: true,
10167            lsp_fetch_timeout_ms: 0,
10168            lsp_insert_mode: LspInsertMode::Insert,
10169        });
10170    });
10171
10172    let mut cx = EditorLspTestContext::new_rust(
10173        lsp::ServerCapabilities {
10174            completion_provider: Some(lsp::CompletionOptions {
10175                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10176                ..lsp::CompletionOptions::default()
10177            }),
10178            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10179            ..lsp::ServerCapabilities::default()
10180        },
10181        cx,
10182    )
10183    .await;
10184
10185    let _completion_requests_handler =
10186        cx.lsp
10187            .server
10188            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10189                Ok(Some(lsp::CompletionResponse::Array(vec![
10190                    lsp::CompletionItem {
10191                        label: "first".into(),
10192                        ..lsp::CompletionItem::default()
10193                    },
10194                    lsp::CompletionItem {
10195                        label: "last".into(),
10196                        ..lsp::CompletionItem::default()
10197                    },
10198                ])))
10199            });
10200
10201    cx.set_state(indoc! {"ˇ
10202        first
10203        last
10204        second
10205    "});
10206    cx.simulate_keystroke(".");
10207    cx.executor().run_until_parked();
10208    cx.condition(|editor, _| editor.context_menu_visible())
10209        .await;
10210    cx.update_editor(|editor, _, _| {
10211        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10212        {
10213            assert_eq!(
10214                completion_menu_entries(&menu),
10215                &["first", "last", "second"],
10216                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10217            );
10218        } else {
10219            panic!("expected completion menu to be open");
10220        }
10221    });
10222}
10223
10224#[gpui::test]
10225async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10226    init_test(cx, |language_settings| {
10227        language_settings.defaults.completions = Some(CompletionSettings {
10228            words: WordsCompletionMode::Disabled,
10229            lsp: true,
10230            lsp_fetch_timeout_ms: 0,
10231            lsp_insert_mode: LspInsertMode::Insert,
10232        });
10233    });
10234
10235    let mut cx = EditorLspTestContext::new_rust(
10236        lsp::ServerCapabilities {
10237            completion_provider: Some(lsp::CompletionOptions {
10238                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10239                ..lsp::CompletionOptions::default()
10240            }),
10241            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10242            ..lsp::ServerCapabilities::default()
10243        },
10244        cx,
10245    )
10246    .await;
10247
10248    let _completion_requests_handler =
10249        cx.lsp
10250            .server
10251            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10252                panic!("LSP completions should not be queried when dealing with word completions")
10253            });
10254
10255    cx.set_state(indoc! {"ˇ
10256        first
10257        last
10258        second
10259    "});
10260    cx.update_editor(|editor, window, cx| {
10261        editor.show_word_completions(&ShowWordCompletions, window, cx);
10262    });
10263    cx.executor().run_until_parked();
10264    cx.condition(|editor, _| editor.context_menu_visible())
10265        .await;
10266    cx.update_editor(|editor, _, _| {
10267        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10268        {
10269            assert_eq!(
10270                completion_menu_entries(&menu),
10271                &["first", "last", "second"],
10272                "`ShowWordCompletions` action should show word completions"
10273            );
10274        } else {
10275            panic!("expected completion menu to be open");
10276        }
10277    });
10278
10279    cx.simulate_keystroke("l");
10280    cx.executor().run_until_parked();
10281    cx.condition(|editor, _| editor.context_menu_visible())
10282        .await;
10283    cx.update_editor(|editor, _, _| {
10284        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10285        {
10286            assert_eq!(
10287                completion_menu_entries(&menu),
10288                &["last"],
10289                "After showing word completions, further editing should filter them and not query the LSP"
10290            );
10291        } else {
10292            panic!("expected completion menu to be open");
10293        }
10294    });
10295}
10296
10297#[gpui::test]
10298async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10299    init_test(cx, |language_settings| {
10300        language_settings.defaults.completions = Some(CompletionSettings {
10301            words: WordsCompletionMode::Fallback,
10302            lsp: false,
10303            lsp_fetch_timeout_ms: 0,
10304            lsp_insert_mode: LspInsertMode::Insert,
10305        });
10306    });
10307
10308    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10309
10310    cx.set_state(indoc! {"ˇ
10311        0_usize
10312        let
10313        33
10314        4.5f32
10315    "});
10316    cx.update_editor(|editor, window, cx| {
10317        editor.show_completions(&ShowCompletions::default(), window, cx);
10318    });
10319    cx.executor().run_until_parked();
10320    cx.condition(|editor, _| editor.context_menu_visible())
10321        .await;
10322    cx.update_editor(|editor, window, cx| {
10323        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10324        {
10325            assert_eq!(
10326                completion_menu_entries(&menu),
10327                &["let"],
10328                "With no digits in the completion query, no digits should be in the word completions"
10329            );
10330        } else {
10331            panic!("expected completion menu to be open");
10332        }
10333        editor.cancel(&Cancel, window, cx);
10334    });
10335
10336    cx.set_state(indoc! {"10337        0_usize
10338        let
10339        3
10340        33.35f32
10341    "});
10342    cx.update_editor(|editor, window, cx| {
10343        editor.show_completions(&ShowCompletions::default(), window, cx);
10344    });
10345    cx.executor().run_until_parked();
10346    cx.condition(|editor, _| editor.context_menu_visible())
10347        .await;
10348    cx.update_editor(|editor, _, _| {
10349        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10350        {
10351            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
10352                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
10353        } else {
10354            panic!("expected completion menu to be open");
10355        }
10356    });
10357}
10358
10359fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
10360    let position = || lsp::Position {
10361        line: params.text_document_position.position.line,
10362        character: params.text_document_position.position.character,
10363    };
10364    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10365        range: lsp::Range {
10366            start: position(),
10367            end: position(),
10368        },
10369        new_text: text.to_string(),
10370    }))
10371}
10372
10373#[gpui::test]
10374async fn test_multiline_completion(cx: &mut TestAppContext) {
10375    init_test(cx, |_| {});
10376
10377    let fs = FakeFs::new(cx.executor());
10378    fs.insert_tree(
10379        path!("/a"),
10380        json!({
10381            "main.ts": "a",
10382        }),
10383    )
10384    .await;
10385
10386    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10387    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10388    let typescript_language = Arc::new(Language::new(
10389        LanguageConfig {
10390            name: "TypeScript".into(),
10391            matcher: LanguageMatcher {
10392                path_suffixes: vec!["ts".to_string()],
10393                ..LanguageMatcher::default()
10394            },
10395            line_comments: vec!["// ".into()],
10396            ..LanguageConfig::default()
10397        },
10398        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10399    ));
10400    language_registry.add(typescript_language.clone());
10401    let mut fake_servers = language_registry.register_fake_lsp(
10402        "TypeScript",
10403        FakeLspAdapter {
10404            capabilities: lsp::ServerCapabilities {
10405                completion_provider: Some(lsp::CompletionOptions {
10406                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10407                    ..lsp::CompletionOptions::default()
10408                }),
10409                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10410                ..lsp::ServerCapabilities::default()
10411            },
10412            // Emulate vtsls label generation
10413            label_for_completion: Some(Box::new(|item, _| {
10414                let text = if let Some(description) = item
10415                    .label_details
10416                    .as_ref()
10417                    .and_then(|label_details| label_details.description.as_ref())
10418                {
10419                    format!("{} {}", item.label, description)
10420                } else if let Some(detail) = &item.detail {
10421                    format!("{} {}", item.label, detail)
10422                } else {
10423                    item.label.clone()
10424                };
10425                let len = text.len();
10426                Some(language::CodeLabel {
10427                    text,
10428                    runs: Vec::new(),
10429                    filter_range: 0..len,
10430                })
10431            })),
10432            ..FakeLspAdapter::default()
10433        },
10434    );
10435    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10436    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10437    let worktree_id = workspace
10438        .update(cx, |workspace, _window, cx| {
10439            workspace.project().update(cx, |project, cx| {
10440                project.worktrees(cx).next().unwrap().read(cx).id()
10441            })
10442        })
10443        .unwrap();
10444    let _buffer = project
10445        .update(cx, |project, cx| {
10446            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
10447        })
10448        .await
10449        .unwrap();
10450    let editor = workspace
10451        .update(cx, |workspace, window, cx| {
10452            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
10453        })
10454        .unwrap()
10455        .await
10456        .unwrap()
10457        .downcast::<Editor>()
10458        .unwrap();
10459    let fake_server = fake_servers.next().await.unwrap();
10460
10461    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
10462    let multiline_label_2 = "a\nb\nc\n";
10463    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
10464    let multiline_description = "d\ne\nf\n";
10465    let multiline_detail_2 = "g\nh\ni\n";
10466
10467    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
10468        move |params, _| async move {
10469            Ok(Some(lsp::CompletionResponse::Array(vec![
10470                lsp::CompletionItem {
10471                    label: multiline_label.to_string(),
10472                    text_edit: gen_text_edit(&params, "new_text_1"),
10473                    ..lsp::CompletionItem::default()
10474                },
10475                lsp::CompletionItem {
10476                    label: "single line label 1".to_string(),
10477                    detail: Some(multiline_detail.to_string()),
10478                    text_edit: gen_text_edit(&params, "new_text_2"),
10479                    ..lsp::CompletionItem::default()
10480                },
10481                lsp::CompletionItem {
10482                    label: "single line label 2".to_string(),
10483                    label_details: Some(lsp::CompletionItemLabelDetails {
10484                        description: Some(multiline_description.to_string()),
10485                        detail: None,
10486                    }),
10487                    text_edit: gen_text_edit(&params, "new_text_2"),
10488                    ..lsp::CompletionItem::default()
10489                },
10490                lsp::CompletionItem {
10491                    label: multiline_label_2.to_string(),
10492                    detail: Some(multiline_detail_2.to_string()),
10493                    text_edit: gen_text_edit(&params, "new_text_3"),
10494                    ..lsp::CompletionItem::default()
10495                },
10496                lsp::CompletionItem {
10497                    label: "Label with many     spaces and \t but without newlines".to_string(),
10498                    detail: Some(
10499                        "Details with many     spaces and \t but without newlines".to_string(),
10500                    ),
10501                    text_edit: gen_text_edit(&params, "new_text_4"),
10502                    ..lsp::CompletionItem::default()
10503                },
10504            ])))
10505        },
10506    );
10507
10508    editor.update_in(cx, |editor, window, cx| {
10509        cx.focus_self(window);
10510        editor.move_to_end(&MoveToEnd, window, cx);
10511        editor.handle_input(".", window, cx);
10512    });
10513    cx.run_until_parked();
10514    completion_handle.next().await.unwrap();
10515
10516    editor.update(cx, |editor, _| {
10517        assert!(editor.context_menu_visible());
10518        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10519        {
10520            let completion_labels = menu
10521                .completions
10522                .borrow()
10523                .iter()
10524                .map(|c| c.label.text.clone())
10525                .collect::<Vec<_>>();
10526            assert_eq!(
10527                completion_labels,
10528                &[
10529                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
10530                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
10531                    "single line label 2 d e f ",
10532                    "a b c g h i ",
10533                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
10534                ],
10535                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
10536            );
10537
10538            for completion in menu
10539                .completions
10540                .borrow()
10541                .iter() {
10542                    assert_eq!(
10543                        completion.label.filter_range,
10544                        0..completion.label.text.len(),
10545                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
10546                    );
10547                }
10548        } else {
10549            panic!("expected completion menu to be open");
10550        }
10551    });
10552}
10553
10554#[gpui::test]
10555async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
10556    init_test(cx, |_| {});
10557    let mut cx = EditorLspTestContext::new_rust(
10558        lsp::ServerCapabilities {
10559            completion_provider: Some(lsp::CompletionOptions {
10560                trigger_characters: Some(vec![".".to_string()]),
10561                ..Default::default()
10562            }),
10563            ..Default::default()
10564        },
10565        cx,
10566    )
10567    .await;
10568    cx.lsp
10569        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10570            Ok(Some(lsp::CompletionResponse::Array(vec![
10571                lsp::CompletionItem {
10572                    label: "first".into(),
10573                    ..Default::default()
10574                },
10575                lsp::CompletionItem {
10576                    label: "last".into(),
10577                    ..Default::default()
10578                },
10579            ])))
10580        });
10581    cx.set_state("variableˇ");
10582    cx.simulate_keystroke(".");
10583    cx.executor().run_until_parked();
10584
10585    cx.update_editor(|editor, _, _| {
10586        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10587        {
10588            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
10589        } else {
10590            panic!("expected completion menu to be open");
10591        }
10592    });
10593
10594    cx.update_editor(|editor, window, cx| {
10595        editor.move_page_down(&MovePageDown::default(), window, cx);
10596        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10597        {
10598            assert!(
10599                menu.selected_item == 1,
10600                "expected PageDown to select the last item from the context menu"
10601            );
10602        } else {
10603            panic!("expected completion menu to stay open after PageDown");
10604        }
10605    });
10606
10607    cx.update_editor(|editor, window, cx| {
10608        editor.move_page_up(&MovePageUp::default(), window, cx);
10609        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10610        {
10611            assert!(
10612                menu.selected_item == 0,
10613                "expected PageUp to select the first item from the context menu"
10614            );
10615        } else {
10616            panic!("expected completion menu to stay open after PageUp");
10617        }
10618    });
10619}
10620
10621#[gpui::test]
10622async fn test_completion_sort(cx: &mut TestAppContext) {
10623    init_test(cx, |_| {});
10624    let mut cx = EditorLspTestContext::new_rust(
10625        lsp::ServerCapabilities {
10626            completion_provider: Some(lsp::CompletionOptions {
10627                trigger_characters: Some(vec![".".to_string()]),
10628                ..Default::default()
10629            }),
10630            ..Default::default()
10631        },
10632        cx,
10633    )
10634    .await;
10635    cx.lsp
10636        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10637            Ok(Some(lsp::CompletionResponse::Array(vec![
10638                lsp::CompletionItem {
10639                    label: "Range".into(),
10640                    sort_text: Some("a".into()),
10641                    ..Default::default()
10642                },
10643                lsp::CompletionItem {
10644                    label: "r".into(),
10645                    sort_text: Some("b".into()),
10646                    ..Default::default()
10647                },
10648                lsp::CompletionItem {
10649                    label: "ret".into(),
10650                    sort_text: Some("c".into()),
10651                    ..Default::default()
10652                },
10653                lsp::CompletionItem {
10654                    label: "return".into(),
10655                    sort_text: Some("d".into()),
10656                    ..Default::default()
10657                },
10658                lsp::CompletionItem {
10659                    label: "slice".into(),
10660                    sort_text: Some("d".into()),
10661                    ..Default::default()
10662                },
10663            ])))
10664        });
10665    cx.set_state("");
10666    cx.executor().run_until_parked();
10667    cx.update_editor(|editor, window, cx| {
10668        editor.show_completions(
10669            &ShowCompletions {
10670                trigger: Some("r".into()),
10671            },
10672            window,
10673            cx,
10674        );
10675    });
10676    cx.executor().run_until_parked();
10677
10678    cx.update_editor(|editor, _, _| {
10679        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10680        {
10681            assert_eq!(
10682                completion_menu_entries(&menu),
10683                &["r", "ret", "Range", "return"]
10684            );
10685        } else {
10686            panic!("expected completion menu to be open");
10687        }
10688    });
10689}
10690
10691#[gpui::test]
10692async fn test_as_is_completions(cx: &mut TestAppContext) {
10693    init_test(cx, |_| {});
10694    let mut cx = EditorLspTestContext::new_rust(
10695        lsp::ServerCapabilities {
10696            completion_provider: Some(lsp::CompletionOptions {
10697                ..Default::default()
10698            }),
10699            ..Default::default()
10700        },
10701        cx,
10702    )
10703    .await;
10704    cx.lsp
10705        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10706            Ok(Some(lsp::CompletionResponse::Array(vec![
10707                lsp::CompletionItem {
10708                    label: "unsafe".into(),
10709                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10710                        range: lsp::Range {
10711                            start: lsp::Position {
10712                                line: 1,
10713                                character: 2,
10714                            },
10715                            end: lsp::Position {
10716                                line: 1,
10717                                character: 3,
10718                            },
10719                        },
10720                        new_text: "unsafe".to_string(),
10721                    })),
10722                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
10723                    ..Default::default()
10724                },
10725            ])))
10726        });
10727    cx.set_state("fn a() {}\n");
10728    cx.executor().run_until_parked();
10729    cx.update_editor(|editor, window, cx| {
10730        editor.show_completions(
10731            &ShowCompletions {
10732                trigger: Some("\n".into()),
10733            },
10734            window,
10735            cx,
10736        );
10737    });
10738    cx.executor().run_until_parked();
10739
10740    cx.update_editor(|editor, window, cx| {
10741        editor.confirm_completion(&Default::default(), window, cx)
10742    });
10743    cx.executor().run_until_parked();
10744    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
10745}
10746
10747#[gpui::test]
10748async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
10749    init_test(cx, |_| {});
10750
10751    let mut cx = EditorLspTestContext::new_rust(
10752        lsp::ServerCapabilities {
10753            completion_provider: Some(lsp::CompletionOptions {
10754                trigger_characters: Some(vec![".".to_string()]),
10755                resolve_provider: Some(true),
10756                ..Default::default()
10757            }),
10758            ..Default::default()
10759        },
10760        cx,
10761    )
10762    .await;
10763
10764    cx.set_state("fn main() { let a = 2ˇ; }");
10765    cx.simulate_keystroke(".");
10766    let completion_item = lsp::CompletionItem {
10767        label: "Some".into(),
10768        kind: Some(lsp::CompletionItemKind::SNIPPET),
10769        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10770        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10771            kind: lsp::MarkupKind::Markdown,
10772            value: "```rust\nSome(2)\n```".to_string(),
10773        })),
10774        deprecated: Some(false),
10775        sort_text: Some("Some".to_string()),
10776        filter_text: Some("Some".to_string()),
10777        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10778        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10779            range: lsp::Range {
10780                start: lsp::Position {
10781                    line: 0,
10782                    character: 22,
10783                },
10784                end: lsp::Position {
10785                    line: 0,
10786                    character: 22,
10787                },
10788            },
10789            new_text: "Some(2)".to_string(),
10790        })),
10791        additional_text_edits: Some(vec![lsp::TextEdit {
10792            range: lsp::Range {
10793                start: lsp::Position {
10794                    line: 0,
10795                    character: 20,
10796                },
10797                end: lsp::Position {
10798                    line: 0,
10799                    character: 22,
10800                },
10801            },
10802            new_text: "".to_string(),
10803        }]),
10804        ..Default::default()
10805    };
10806
10807    let closure_completion_item = completion_item.clone();
10808    let counter = Arc::new(AtomicUsize::new(0));
10809    let counter_clone = counter.clone();
10810    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
10811        let task_completion_item = closure_completion_item.clone();
10812        counter_clone.fetch_add(1, atomic::Ordering::Release);
10813        async move {
10814            Ok(Some(lsp::CompletionResponse::Array(vec![
10815                task_completion_item,
10816            ])))
10817        }
10818    });
10819
10820    cx.condition(|editor, _| editor.context_menu_visible())
10821        .await;
10822    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
10823    assert!(request.next().await.is_some());
10824    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10825
10826    cx.simulate_keystrokes("S o m");
10827    cx.condition(|editor, _| editor.context_menu_visible())
10828        .await;
10829    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
10830    assert!(request.next().await.is_some());
10831    assert!(request.next().await.is_some());
10832    assert!(request.next().await.is_some());
10833    request.close();
10834    assert!(request.next().await.is_none());
10835    assert_eq!(
10836        counter.load(atomic::Ordering::Acquire),
10837        4,
10838        "With the completions menu open, only one LSP request should happen per input"
10839    );
10840}
10841
10842#[gpui::test]
10843async fn test_toggle_comment(cx: &mut TestAppContext) {
10844    init_test(cx, |_| {});
10845    let mut cx = EditorTestContext::new(cx).await;
10846    let language = Arc::new(Language::new(
10847        LanguageConfig {
10848            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10849            ..Default::default()
10850        },
10851        Some(tree_sitter_rust::LANGUAGE.into()),
10852    ));
10853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10854
10855    // If multiple selections intersect a line, the line is only toggled once.
10856    cx.set_state(indoc! {"
10857        fn a() {
10858            «//b();
10859            ˇ»// «c();
10860            //ˇ»  d();
10861        }
10862    "});
10863
10864    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10865
10866    cx.assert_editor_state(indoc! {"
10867        fn a() {
10868            «b();
10869            c();
10870            ˇ» d();
10871        }
10872    "});
10873
10874    // The comment prefix is inserted at the same column for every line in a
10875    // selection.
10876    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10877
10878    cx.assert_editor_state(indoc! {"
10879        fn a() {
10880            // «b();
10881            // c();
10882            ˇ»//  d();
10883        }
10884    "});
10885
10886    // If a selection ends at the beginning of a line, that line is not toggled.
10887    cx.set_selections_state(indoc! {"
10888        fn a() {
10889            // b();
10890            «// c();
10891        ˇ»    //  d();
10892        }
10893    "});
10894
10895    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10896
10897    cx.assert_editor_state(indoc! {"
10898        fn a() {
10899            // b();
10900            «c();
10901        ˇ»    //  d();
10902        }
10903    "});
10904
10905    // If a selection span a single line and is empty, the line is toggled.
10906    cx.set_state(indoc! {"
10907        fn a() {
10908            a();
10909            b();
10910        ˇ
10911        }
10912    "});
10913
10914    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10915
10916    cx.assert_editor_state(indoc! {"
10917        fn a() {
10918            a();
10919            b();
10920        //•ˇ
10921        }
10922    "});
10923
10924    // If a selection span multiple lines, empty lines are not toggled.
10925    cx.set_state(indoc! {"
10926        fn a() {
10927            «a();
10928
10929            c();ˇ»
10930        }
10931    "});
10932
10933    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10934
10935    cx.assert_editor_state(indoc! {"
10936        fn a() {
10937            // «a();
10938
10939            // c();ˇ»
10940        }
10941    "});
10942
10943    // If a selection includes multiple comment prefixes, all lines are uncommented.
10944    cx.set_state(indoc! {"
10945        fn a() {
10946            «// a();
10947            /// b();
10948            //! c();ˇ»
10949        }
10950    "});
10951
10952    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10953
10954    cx.assert_editor_state(indoc! {"
10955        fn a() {
10956            «a();
10957            b();
10958            c();ˇ»
10959        }
10960    "});
10961}
10962
10963#[gpui::test]
10964async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10965    init_test(cx, |_| {});
10966    let mut cx = EditorTestContext::new(cx).await;
10967    let language = Arc::new(Language::new(
10968        LanguageConfig {
10969            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10970            ..Default::default()
10971        },
10972        Some(tree_sitter_rust::LANGUAGE.into()),
10973    ));
10974    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10975
10976    let toggle_comments = &ToggleComments {
10977        advance_downwards: false,
10978        ignore_indent: true,
10979    };
10980
10981    // If multiple selections intersect a line, the line is only toggled once.
10982    cx.set_state(indoc! {"
10983        fn a() {
10984        //    «b();
10985        //    c();
10986        //    ˇ» d();
10987        }
10988    "});
10989
10990    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10991
10992    cx.assert_editor_state(indoc! {"
10993        fn a() {
10994            «b();
10995            c();
10996            ˇ» d();
10997        }
10998    "});
10999
11000    // The comment prefix is inserted at the beginning of each line
11001    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11002
11003    cx.assert_editor_state(indoc! {"
11004        fn a() {
11005        //    «b();
11006        //    c();
11007        //    ˇ» d();
11008        }
11009    "});
11010
11011    // If a selection ends at the beginning of a line, that line is not toggled.
11012    cx.set_selections_state(indoc! {"
11013        fn a() {
11014        //    b();
11015        //    «c();
11016        ˇ»//     d();
11017        }
11018    "});
11019
11020    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11021
11022    cx.assert_editor_state(indoc! {"
11023        fn a() {
11024        //    b();
11025            «c();
11026        ˇ»//     d();
11027        }
11028    "});
11029
11030    // If a selection span a single line and is empty, the line is toggled.
11031    cx.set_state(indoc! {"
11032        fn a() {
11033            a();
11034            b();
11035        ˇ
11036        }
11037    "});
11038
11039    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11040
11041    cx.assert_editor_state(indoc! {"
11042        fn a() {
11043            a();
11044            b();
11045        //ˇ
11046        }
11047    "});
11048
11049    // If a selection span multiple lines, empty lines are not toggled.
11050    cx.set_state(indoc! {"
11051        fn a() {
11052            «a();
11053
11054            c();ˇ»
11055        }
11056    "});
11057
11058    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11059
11060    cx.assert_editor_state(indoc! {"
11061        fn a() {
11062        //    «a();
11063
11064        //    c();ˇ»
11065        }
11066    "});
11067
11068    // If a selection includes multiple comment prefixes, all lines are uncommented.
11069    cx.set_state(indoc! {"
11070        fn a() {
11071        //    «a();
11072        ///    b();
11073        //!    c();ˇ»
11074        }
11075    "});
11076
11077    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11078
11079    cx.assert_editor_state(indoc! {"
11080        fn a() {
11081            «a();
11082            b();
11083            c();ˇ»
11084        }
11085    "});
11086}
11087
11088#[gpui::test]
11089async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11090    init_test(cx, |_| {});
11091
11092    let language = Arc::new(Language::new(
11093        LanguageConfig {
11094            line_comments: vec!["// ".into()],
11095            ..Default::default()
11096        },
11097        Some(tree_sitter_rust::LANGUAGE.into()),
11098    ));
11099
11100    let mut cx = EditorTestContext::new(cx).await;
11101
11102    cx.language_registry().add(language.clone());
11103    cx.update_buffer(|buffer, cx| {
11104        buffer.set_language(Some(language), cx);
11105    });
11106
11107    let toggle_comments = &ToggleComments {
11108        advance_downwards: true,
11109        ignore_indent: false,
11110    };
11111
11112    // Single cursor on one line -> advance
11113    // Cursor moves horizontally 3 characters as well on non-blank line
11114    cx.set_state(indoc!(
11115        "fn a() {
11116             ˇdog();
11117             cat();
11118        }"
11119    ));
11120    cx.update_editor(|editor, window, cx| {
11121        editor.toggle_comments(toggle_comments, window, cx);
11122    });
11123    cx.assert_editor_state(indoc!(
11124        "fn a() {
11125             // dog();
11126             catˇ();
11127        }"
11128    ));
11129
11130    // Single selection on one line -> don't advance
11131    cx.set_state(indoc!(
11132        "fn a() {
11133             «dog()ˇ»;
11134             cat();
11135        }"
11136    ));
11137    cx.update_editor(|editor, window, cx| {
11138        editor.toggle_comments(toggle_comments, window, cx);
11139    });
11140    cx.assert_editor_state(indoc!(
11141        "fn a() {
11142             // «dog()ˇ»;
11143             cat();
11144        }"
11145    ));
11146
11147    // Multiple cursors on one line -> advance
11148    cx.set_state(indoc!(
11149        "fn a() {
11150             ˇdˇog();
11151             cat();
11152        }"
11153    ));
11154    cx.update_editor(|editor, window, cx| {
11155        editor.toggle_comments(toggle_comments, window, cx);
11156    });
11157    cx.assert_editor_state(indoc!(
11158        "fn a() {
11159             // dog();
11160             catˇ(ˇ);
11161        }"
11162    ));
11163
11164    // Multiple cursors on one line, with selection -> don't advance
11165    cx.set_state(indoc!(
11166        "fn a() {
11167             ˇdˇog«()ˇ»;
11168             cat();
11169        }"
11170    ));
11171    cx.update_editor(|editor, window, cx| {
11172        editor.toggle_comments(toggle_comments, window, cx);
11173    });
11174    cx.assert_editor_state(indoc!(
11175        "fn a() {
11176             // ˇdˇog«()ˇ»;
11177             cat();
11178        }"
11179    ));
11180
11181    // Single cursor on one line -> advance
11182    // Cursor moves to column 0 on blank line
11183    cx.set_state(indoc!(
11184        "fn a() {
11185             ˇdog();
11186
11187             cat();
11188        }"
11189    ));
11190    cx.update_editor(|editor, window, cx| {
11191        editor.toggle_comments(toggle_comments, window, cx);
11192    });
11193    cx.assert_editor_state(indoc!(
11194        "fn a() {
11195             // dog();
11196        ˇ
11197             cat();
11198        }"
11199    ));
11200
11201    // Single cursor on one line -> advance
11202    // Cursor starts and ends at column 0
11203    cx.set_state(indoc!(
11204        "fn a() {
11205         ˇ    dog();
11206             cat();
11207        }"
11208    ));
11209    cx.update_editor(|editor, window, cx| {
11210        editor.toggle_comments(toggle_comments, window, cx);
11211    });
11212    cx.assert_editor_state(indoc!(
11213        "fn a() {
11214             // dog();
11215         ˇ    cat();
11216        }"
11217    ));
11218}
11219
11220#[gpui::test]
11221async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11222    init_test(cx, |_| {});
11223
11224    let mut cx = EditorTestContext::new(cx).await;
11225
11226    let html_language = Arc::new(
11227        Language::new(
11228            LanguageConfig {
11229                name: "HTML".into(),
11230                block_comment: Some(("<!-- ".into(), " -->".into())),
11231                ..Default::default()
11232            },
11233            Some(tree_sitter_html::LANGUAGE.into()),
11234        )
11235        .with_injection_query(
11236            r#"
11237            (script_element
11238                (raw_text) @injection.content
11239                (#set! injection.language "javascript"))
11240            "#,
11241        )
11242        .unwrap(),
11243    );
11244
11245    let javascript_language = Arc::new(Language::new(
11246        LanguageConfig {
11247            name: "JavaScript".into(),
11248            line_comments: vec!["// ".into()],
11249            ..Default::default()
11250        },
11251        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11252    ));
11253
11254    cx.language_registry().add(html_language.clone());
11255    cx.language_registry().add(javascript_language.clone());
11256    cx.update_buffer(|buffer, cx| {
11257        buffer.set_language(Some(html_language), cx);
11258    });
11259
11260    // Toggle comments for empty selections
11261    cx.set_state(
11262        &r#"
11263            <p>A</p>ˇ
11264            <p>B</p>ˇ
11265            <p>C</p>ˇ
11266        "#
11267        .unindent(),
11268    );
11269    cx.update_editor(|editor, window, cx| {
11270        editor.toggle_comments(&ToggleComments::default(), window, cx)
11271    });
11272    cx.assert_editor_state(
11273        &r#"
11274            <!-- <p>A</p>ˇ -->
11275            <!-- <p>B</p>ˇ -->
11276            <!-- <p>C</p>ˇ -->
11277        "#
11278        .unindent(),
11279    );
11280    cx.update_editor(|editor, window, cx| {
11281        editor.toggle_comments(&ToggleComments::default(), window, cx)
11282    });
11283    cx.assert_editor_state(
11284        &r#"
11285            <p>A</p>ˇ
11286            <p>B</p>ˇ
11287            <p>C</p>ˇ
11288        "#
11289        .unindent(),
11290    );
11291
11292    // Toggle comments for mixture of empty and non-empty selections, where
11293    // multiple selections occupy a given line.
11294    cx.set_state(
11295        &r#"
11296            <p>A«</p>
11297            <p>ˇ»B</p>ˇ
11298            <p>C«</p>
11299            <p>ˇ»D</p>ˇ
11300        "#
11301        .unindent(),
11302    );
11303
11304    cx.update_editor(|editor, window, cx| {
11305        editor.toggle_comments(&ToggleComments::default(), window, cx)
11306    });
11307    cx.assert_editor_state(
11308        &r#"
11309            <!-- <p>A«</p>
11310            <p>ˇ»B</p>ˇ -->
11311            <!-- <p>C«</p>
11312            <p>ˇ»D</p>ˇ -->
11313        "#
11314        .unindent(),
11315    );
11316    cx.update_editor(|editor, window, cx| {
11317        editor.toggle_comments(&ToggleComments::default(), window, cx)
11318    });
11319    cx.assert_editor_state(
11320        &r#"
11321            <p>A«</p>
11322            <p>ˇ»B</p>ˇ
11323            <p>C«</p>
11324            <p>ˇ»D</p>ˇ
11325        "#
11326        .unindent(),
11327    );
11328
11329    // Toggle comments when different languages are active for different
11330    // selections.
11331    cx.set_state(
11332        &r#"
11333            ˇ<script>
11334                ˇvar x = new Y();
11335            ˇ</script>
11336        "#
11337        .unindent(),
11338    );
11339    cx.executor().run_until_parked();
11340    cx.update_editor(|editor, window, cx| {
11341        editor.toggle_comments(&ToggleComments::default(), window, cx)
11342    });
11343    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11344    // Uncommenting and commenting from this position brings in even more wrong artifacts.
11345    cx.assert_editor_state(
11346        &r#"
11347            <!-- ˇ<script> -->
11348                // ˇvar x = new Y();
11349            <!-- ˇ</script> -->
11350        "#
11351        .unindent(),
11352    );
11353}
11354
11355#[gpui::test]
11356fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11357    init_test(cx, |_| {});
11358
11359    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11360    let multibuffer = cx.new(|cx| {
11361        let mut multibuffer = MultiBuffer::new(ReadWrite);
11362        multibuffer.push_excerpts(
11363            buffer.clone(),
11364            [
11365                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11366                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11367            ],
11368            cx,
11369        );
11370        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11371        multibuffer
11372    });
11373
11374    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11375    editor.update_in(cx, |editor, window, cx| {
11376        assert_eq!(editor.text(cx), "aaaa\nbbbb");
11377        editor.change_selections(None, window, cx, |s| {
11378            s.select_ranges([
11379                Point::new(0, 0)..Point::new(0, 0),
11380                Point::new(1, 0)..Point::new(1, 0),
11381            ])
11382        });
11383
11384        editor.handle_input("X", window, cx);
11385        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
11386        assert_eq!(
11387            editor.selections.ranges(cx),
11388            [
11389                Point::new(0, 1)..Point::new(0, 1),
11390                Point::new(1, 1)..Point::new(1, 1),
11391            ]
11392        );
11393
11394        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
11395        editor.change_selections(None, window, cx, |s| {
11396            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
11397        });
11398        editor.backspace(&Default::default(), window, cx);
11399        assert_eq!(editor.text(cx), "Xa\nbbb");
11400        assert_eq!(
11401            editor.selections.ranges(cx),
11402            [Point::new(1, 0)..Point::new(1, 0)]
11403        );
11404
11405        editor.change_selections(None, window, cx, |s| {
11406            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
11407        });
11408        editor.backspace(&Default::default(), window, cx);
11409        assert_eq!(editor.text(cx), "X\nbb");
11410        assert_eq!(
11411            editor.selections.ranges(cx),
11412            [Point::new(0, 1)..Point::new(0, 1)]
11413        );
11414    });
11415}
11416
11417#[gpui::test]
11418fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
11419    init_test(cx, |_| {});
11420
11421    let markers = vec![('[', ']').into(), ('(', ')').into()];
11422    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
11423        indoc! {"
11424            [aaaa
11425            (bbbb]
11426            cccc)",
11427        },
11428        markers.clone(),
11429    );
11430    let excerpt_ranges = markers.into_iter().map(|marker| {
11431        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
11432        ExcerptRange::new(context.clone())
11433    });
11434    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
11435    let multibuffer = cx.new(|cx| {
11436        let mut multibuffer = MultiBuffer::new(ReadWrite);
11437        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
11438        multibuffer
11439    });
11440
11441    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11442    editor.update_in(cx, |editor, window, cx| {
11443        let (expected_text, selection_ranges) = marked_text_ranges(
11444            indoc! {"
11445                aaaa
11446                bˇbbb
11447                bˇbbˇb
11448                cccc"
11449            },
11450            true,
11451        );
11452        assert_eq!(editor.text(cx), expected_text);
11453        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
11454
11455        editor.handle_input("X", window, cx);
11456
11457        let (expected_text, expected_selections) = marked_text_ranges(
11458            indoc! {"
11459                aaaa
11460                bXˇbbXb
11461                bXˇbbXˇb
11462                cccc"
11463            },
11464            false,
11465        );
11466        assert_eq!(editor.text(cx), expected_text);
11467        assert_eq!(editor.selections.ranges(cx), expected_selections);
11468
11469        editor.newline(&Newline, window, cx);
11470        let (expected_text, expected_selections) = marked_text_ranges(
11471            indoc! {"
11472                aaaa
11473                bX
11474                ˇbbX
11475                b
11476                bX
11477                ˇbbX
11478                ˇb
11479                cccc"
11480            },
11481            false,
11482        );
11483        assert_eq!(editor.text(cx), expected_text);
11484        assert_eq!(editor.selections.ranges(cx), expected_selections);
11485    });
11486}
11487
11488#[gpui::test]
11489fn test_refresh_selections(cx: &mut TestAppContext) {
11490    init_test(cx, |_| {});
11491
11492    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11493    let mut excerpt1_id = None;
11494    let multibuffer = cx.new(|cx| {
11495        let mut multibuffer = MultiBuffer::new(ReadWrite);
11496        excerpt1_id = multibuffer
11497            .push_excerpts(
11498                buffer.clone(),
11499                [
11500                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
11501                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
11502                ],
11503                cx,
11504            )
11505            .into_iter()
11506            .next();
11507        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
11508        multibuffer
11509    });
11510
11511    let editor = cx.add_window(|window, cx| {
11512        let mut editor = build_editor(multibuffer.clone(), window, cx);
11513        let snapshot = editor.snapshot(window, cx);
11514        editor.change_selections(None, window, cx, |s| {
11515            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
11516        });
11517        editor.begin_selection(
11518            Point::new(2, 1).to_display_point(&snapshot),
11519            true,
11520            1,
11521            window,
11522            cx,
11523        );
11524        assert_eq!(
11525            editor.selections.ranges(cx),
11526            [
11527                Point::new(1, 3)..Point::new(1, 3),
11528                Point::new(2, 1)..Point::new(2, 1),
11529            ]
11530        );
11531        editor
11532    });
11533
11534    // Refreshing selections is a no-op when excerpts haven't changed.
11535    _ = editor.update(cx, |editor, window, cx| {
11536        editor.change_selections(None, window, cx, |s| s.refresh());
11537        assert_eq!(
11538            editor.selections.ranges(cx),
11539            [
11540                Point::new(1, 3)..Point::new(1, 3),
11541                Point::new(2, 1)..Point::new(2, 1),
11542            ]
11543        );
11544    });
11545
11546    multibuffer.update(cx, |multibuffer, cx| {
11547        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11548    });
11549    _ = editor.update(cx, |editor, window, cx| {
11550        // Removing an excerpt causes the first selection to become degenerate.
11551        assert_eq!(
11552            editor.selections.ranges(cx),
11553            [
11554                Point::new(0, 0)..Point::new(0, 0),
11555                Point::new(0, 1)..Point::new(0, 1)
11556            ]
11557        );
11558
11559        // Refreshing selections will relocate the first selection to the original buffer
11560        // location.
11561        editor.change_selections(None, window, cx, |s| s.refresh());
11562        assert_eq!(
11563            editor.selections.ranges(cx),
11564            [
11565                Point::new(0, 1)..Point::new(0, 1),
11566                Point::new(0, 3)..Point::new(0, 3)
11567            ]
11568        );
11569        assert!(editor.selections.pending_anchor().is_some());
11570    });
11571}
11572
11573#[gpui::test]
11574fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
11575    init_test(cx, |_| {});
11576
11577    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11578    let mut excerpt1_id = None;
11579    let multibuffer = cx.new(|cx| {
11580        let mut multibuffer = MultiBuffer::new(ReadWrite);
11581        excerpt1_id = multibuffer
11582            .push_excerpts(
11583                buffer.clone(),
11584                [
11585                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
11586                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
11587                ],
11588                cx,
11589            )
11590            .into_iter()
11591            .next();
11592        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
11593        multibuffer
11594    });
11595
11596    let editor = cx.add_window(|window, cx| {
11597        let mut editor = build_editor(multibuffer.clone(), window, cx);
11598        let snapshot = editor.snapshot(window, cx);
11599        editor.begin_selection(
11600            Point::new(1, 3).to_display_point(&snapshot),
11601            false,
11602            1,
11603            window,
11604            cx,
11605        );
11606        assert_eq!(
11607            editor.selections.ranges(cx),
11608            [Point::new(1, 3)..Point::new(1, 3)]
11609        );
11610        editor
11611    });
11612
11613    multibuffer.update(cx, |multibuffer, cx| {
11614        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11615    });
11616    _ = editor.update(cx, |editor, window, cx| {
11617        assert_eq!(
11618            editor.selections.ranges(cx),
11619            [Point::new(0, 0)..Point::new(0, 0)]
11620        );
11621
11622        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
11623        editor.change_selections(None, window, cx, |s| s.refresh());
11624        assert_eq!(
11625            editor.selections.ranges(cx),
11626            [Point::new(0, 3)..Point::new(0, 3)]
11627        );
11628        assert!(editor.selections.pending_anchor().is_some());
11629    });
11630}
11631
11632#[gpui::test]
11633async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
11634    init_test(cx, |_| {});
11635
11636    let language = Arc::new(
11637        Language::new(
11638            LanguageConfig {
11639                brackets: BracketPairConfig {
11640                    pairs: vec![
11641                        BracketPair {
11642                            start: "{".to_string(),
11643                            end: "}".to_string(),
11644                            close: true,
11645                            surround: true,
11646                            newline: true,
11647                        },
11648                        BracketPair {
11649                            start: "/* ".to_string(),
11650                            end: " */".to_string(),
11651                            close: true,
11652                            surround: true,
11653                            newline: true,
11654                        },
11655                    ],
11656                    ..Default::default()
11657                },
11658                ..Default::default()
11659            },
11660            Some(tree_sitter_rust::LANGUAGE.into()),
11661        )
11662        .with_indents_query("")
11663        .unwrap(),
11664    );
11665
11666    let text = concat!(
11667        "{   }\n",     //
11668        "  x\n",       //
11669        "  /*   */\n", //
11670        "x\n",         //
11671        "{{} }\n",     //
11672    );
11673
11674    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11675    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11676    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11677    editor
11678        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11679        .await;
11680
11681    editor.update_in(cx, |editor, window, cx| {
11682        editor.change_selections(None, window, cx, |s| {
11683            s.select_display_ranges([
11684                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
11685                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
11686                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
11687            ])
11688        });
11689        editor.newline(&Newline, window, cx);
11690
11691        assert_eq!(
11692            editor.buffer().read(cx).read(cx).text(),
11693            concat!(
11694                "{ \n",    // Suppress rustfmt
11695                "\n",      //
11696                "}\n",     //
11697                "  x\n",   //
11698                "  /* \n", //
11699                "  \n",    //
11700                "  */\n",  //
11701                "x\n",     //
11702                "{{} \n",  //
11703                "}\n",     //
11704            )
11705        );
11706    });
11707}
11708
11709#[gpui::test]
11710fn test_highlighted_ranges(cx: &mut TestAppContext) {
11711    init_test(cx, |_| {});
11712
11713    let editor = cx.add_window(|window, cx| {
11714        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
11715        build_editor(buffer.clone(), window, cx)
11716    });
11717
11718    _ = editor.update(cx, |editor, window, cx| {
11719        struct Type1;
11720        struct Type2;
11721
11722        let buffer = editor.buffer.read(cx).snapshot(cx);
11723
11724        let anchor_range =
11725            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
11726
11727        editor.highlight_background::<Type1>(
11728            &[
11729                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
11730                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
11731                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
11732                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
11733            ],
11734            |_| Hsla::red(),
11735            cx,
11736        );
11737        editor.highlight_background::<Type2>(
11738            &[
11739                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
11740                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
11741                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
11742                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
11743            ],
11744            |_| Hsla::green(),
11745            cx,
11746        );
11747
11748        let snapshot = editor.snapshot(window, cx);
11749        let mut highlighted_ranges = editor.background_highlights_in_range(
11750            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
11751            &snapshot,
11752            cx.theme().colors(),
11753        );
11754        // Enforce a consistent ordering based on color without relying on the ordering of the
11755        // highlight's `TypeId` which is non-executor.
11756        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
11757        assert_eq!(
11758            highlighted_ranges,
11759            &[
11760                (
11761                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
11762                    Hsla::red(),
11763                ),
11764                (
11765                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11766                    Hsla::red(),
11767                ),
11768                (
11769                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
11770                    Hsla::green(),
11771                ),
11772                (
11773                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11774                    Hsla::green(),
11775                ),
11776            ]
11777        );
11778        assert_eq!(
11779            editor.background_highlights_in_range(
11780                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11781                &snapshot,
11782                cx.theme().colors(),
11783            ),
11784            &[(
11785                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11786                Hsla::red(),
11787            )]
11788        );
11789    });
11790}
11791
11792#[gpui::test]
11793async fn test_following(cx: &mut TestAppContext) {
11794    init_test(cx, |_| {});
11795
11796    let fs = FakeFs::new(cx.executor());
11797    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11798
11799    let buffer = project.update(cx, |project, cx| {
11800        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11801        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11802    });
11803    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11804    let follower = cx.update(|cx| {
11805        cx.open_window(
11806            WindowOptions {
11807                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11808                    gpui::Point::new(px(0.), px(0.)),
11809                    gpui::Point::new(px(10.), px(80.)),
11810                ))),
11811                ..Default::default()
11812            },
11813            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11814        )
11815        .unwrap()
11816    });
11817
11818    let is_still_following = Rc::new(RefCell::new(true));
11819    let follower_edit_event_count = Rc::new(RefCell::new(0));
11820    let pending_update = Rc::new(RefCell::new(None));
11821    let leader_entity = leader.root(cx).unwrap();
11822    let follower_entity = follower.root(cx).unwrap();
11823    _ = follower.update(cx, {
11824        let update = pending_update.clone();
11825        let is_still_following = is_still_following.clone();
11826        let follower_edit_event_count = follower_edit_event_count.clone();
11827        |_, window, cx| {
11828            cx.subscribe_in(
11829                &leader_entity,
11830                window,
11831                move |_, leader, event, window, cx| {
11832                    leader.read(cx).add_event_to_update_proto(
11833                        event,
11834                        &mut update.borrow_mut(),
11835                        window,
11836                        cx,
11837                    );
11838                },
11839            )
11840            .detach();
11841
11842            cx.subscribe_in(
11843                &follower_entity,
11844                window,
11845                move |_, _, event: &EditorEvent, _window, _cx| {
11846                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11847                        *is_still_following.borrow_mut() = false;
11848                    }
11849
11850                    if let EditorEvent::BufferEdited = event {
11851                        *follower_edit_event_count.borrow_mut() += 1;
11852                    }
11853                },
11854            )
11855            .detach();
11856        }
11857    });
11858
11859    // Update the selections only
11860    _ = leader.update(cx, |leader, window, cx| {
11861        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11862    });
11863    follower
11864        .update(cx, |follower, window, cx| {
11865            follower.apply_update_proto(
11866                &project,
11867                pending_update.borrow_mut().take().unwrap(),
11868                window,
11869                cx,
11870            )
11871        })
11872        .unwrap()
11873        .await
11874        .unwrap();
11875    _ = follower.update(cx, |follower, _, cx| {
11876        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11877    });
11878    assert!(*is_still_following.borrow());
11879    assert_eq!(*follower_edit_event_count.borrow(), 0);
11880
11881    // Update the scroll position only
11882    _ = leader.update(cx, |leader, window, cx| {
11883        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11884    });
11885    follower
11886        .update(cx, |follower, window, cx| {
11887            follower.apply_update_proto(
11888                &project,
11889                pending_update.borrow_mut().take().unwrap(),
11890                window,
11891                cx,
11892            )
11893        })
11894        .unwrap()
11895        .await
11896        .unwrap();
11897    assert_eq!(
11898        follower
11899            .update(cx, |follower, _, cx| follower.scroll_position(cx))
11900            .unwrap(),
11901        gpui::Point::new(1.5, 3.5)
11902    );
11903    assert!(*is_still_following.borrow());
11904    assert_eq!(*follower_edit_event_count.borrow(), 0);
11905
11906    // Update the selections and scroll position. The follower's scroll position is updated
11907    // via autoscroll, not via the leader's exact scroll position.
11908    _ = leader.update(cx, |leader, window, cx| {
11909        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11910        leader.request_autoscroll(Autoscroll::newest(), cx);
11911        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11912    });
11913    follower
11914        .update(cx, |follower, window, cx| {
11915            follower.apply_update_proto(
11916                &project,
11917                pending_update.borrow_mut().take().unwrap(),
11918                window,
11919                cx,
11920            )
11921        })
11922        .unwrap()
11923        .await
11924        .unwrap();
11925    _ = follower.update(cx, |follower, _, cx| {
11926        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11927        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11928    });
11929    assert!(*is_still_following.borrow());
11930
11931    // Creating a pending selection that precedes another selection
11932    _ = leader.update(cx, |leader, window, cx| {
11933        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11934        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11935    });
11936    follower
11937        .update(cx, |follower, window, cx| {
11938            follower.apply_update_proto(
11939                &project,
11940                pending_update.borrow_mut().take().unwrap(),
11941                window,
11942                cx,
11943            )
11944        })
11945        .unwrap()
11946        .await
11947        .unwrap();
11948    _ = follower.update(cx, |follower, _, cx| {
11949        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11950    });
11951    assert!(*is_still_following.borrow());
11952
11953    // Extend the pending selection so that it surrounds another selection
11954    _ = leader.update(cx, |leader, window, cx| {
11955        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11956    });
11957    follower
11958        .update(cx, |follower, window, cx| {
11959            follower.apply_update_proto(
11960                &project,
11961                pending_update.borrow_mut().take().unwrap(),
11962                window,
11963                cx,
11964            )
11965        })
11966        .unwrap()
11967        .await
11968        .unwrap();
11969    _ = follower.update(cx, |follower, _, cx| {
11970        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11971    });
11972
11973    // Scrolling locally breaks the follow
11974    _ = follower.update(cx, |follower, window, cx| {
11975        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11976        follower.set_scroll_anchor(
11977            ScrollAnchor {
11978                anchor: top_anchor,
11979                offset: gpui::Point::new(0.0, 0.5),
11980            },
11981            window,
11982            cx,
11983        );
11984    });
11985    assert!(!(*is_still_following.borrow()));
11986}
11987
11988#[gpui::test]
11989async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11990    init_test(cx, |_| {});
11991
11992    let fs = FakeFs::new(cx.executor());
11993    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11994    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11995    let pane = workspace
11996        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11997        .unwrap();
11998
11999    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12000
12001    let leader = pane.update_in(cx, |_, window, cx| {
12002        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12003        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12004    });
12005
12006    // Start following the editor when it has no excerpts.
12007    let mut state_message =
12008        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12009    let workspace_entity = workspace.root(cx).unwrap();
12010    let follower_1 = cx
12011        .update_window(*workspace.deref(), |_, window, cx| {
12012            Editor::from_state_proto(
12013                workspace_entity,
12014                ViewId {
12015                    creator: Default::default(),
12016                    id: 0,
12017                },
12018                &mut state_message,
12019                window,
12020                cx,
12021            )
12022        })
12023        .unwrap()
12024        .unwrap()
12025        .await
12026        .unwrap();
12027
12028    let update_message = Rc::new(RefCell::new(None));
12029    follower_1.update_in(cx, {
12030        let update = update_message.clone();
12031        |_, window, cx| {
12032            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12033                leader.read(cx).add_event_to_update_proto(
12034                    event,
12035                    &mut update.borrow_mut(),
12036                    window,
12037                    cx,
12038                );
12039            })
12040            .detach();
12041        }
12042    });
12043
12044    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12045        (
12046            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12047            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12048        )
12049    });
12050
12051    // Insert some excerpts.
12052    leader.update(cx, |leader, cx| {
12053        leader.buffer.update(cx, |multibuffer, cx| {
12054            let excerpt_ids = multibuffer.push_excerpts(
12055                buffer_1.clone(),
12056                [
12057                    ExcerptRange::new(1..6),
12058                    ExcerptRange::new(12..15),
12059                    ExcerptRange::new(0..3),
12060                ],
12061                cx,
12062            );
12063            multibuffer.insert_excerpts_after(
12064                excerpt_ids[0],
12065                buffer_2.clone(),
12066                [ExcerptRange::new(8..12), ExcerptRange::new(0..6)],
12067                cx,
12068            );
12069        });
12070    });
12071
12072    // Apply the update of adding the excerpts.
12073    follower_1
12074        .update_in(cx, |follower, window, cx| {
12075            follower.apply_update_proto(
12076                &project,
12077                update_message.borrow().clone().unwrap(),
12078                window,
12079                cx,
12080            )
12081        })
12082        .await
12083        .unwrap();
12084    assert_eq!(
12085        follower_1.update(cx, |editor, cx| editor.text(cx)),
12086        leader.update(cx, |editor, cx| editor.text(cx))
12087    );
12088    update_message.borrow_mut().take();
12089
12090    // Start following separately after it already has excerpts.
12091    let mut state_message =
12092        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12093    let workspace_entity = workspace.root(cx).unwrap();
12094    let follower_2 = cx
12095        .update_window(*workspace.deref(), |_, window, cx| {
12096            Editor::from_state_proto(
12097                workspace_entity,
12098                ViewId {
12099                    creator: Default::default(),
12100                    id: 0,
12101                },
12102                &mut state_message,
12103                window,
12104                cx,
12105            )
12106        })
12107        .unwrap()
12108        .unwrap()
12109        .await
12110        .unwrap();
12111    assert_eq!(
12112        follower_2.update(cx, |editor, cx| editor.text(cx)),
12113        leader.update(cx, |editor, cx| editor.text(cx))
12114    );
12115
12116    // Remove some excerpts.
12117    leader.update(cx, |leader, cx| {
12118        leader.buffer.update(cx, |multibuffer, cx| {
12119            let excerpt_ids = multibuffer.excerpt_ids();
12120            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12121            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12122        });
12123    });
12124
12125    // Apply the update of removing the excerpts.
12126    follower_1
12127        .update_in(cx, |follower, window, cx| {
12128            follower.apply_update_proto(
12129                &project,
12130                update_message.borrow().clone().unwrap(),
12131                window,
12132                cx,
12133            )
12134        })
12135        .await
12136        .unwrap();
12137    follower_2
12138        .update_in(cx, |follower, window, cx| {
12139            follower.apply_update_proto(
12140                &project,
12141                update_message.borrow().clone().unwrap(),
12142                window,
12143                cx,
12144            )
12145        })
12146        .await
12147        .unwrap();
12148    update_message.borrow_mut().take();
12149    assert_eq!(
12150        follower_1.update(cx, |editor, cx| editor.text(cx)),
12151        leader.update(cx, |editor, cx| editor.text(cx))
12152    );
12153}
12154
12155#[gpui::test]
12156async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12157    init_test(cx, |_| {});
12158
12159    let mut cx = EditorTestContext::new(cx).await;
12160    let lsp_store =
12161        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12162
12163    cx.set_state(indoc! {"
12164        ˇfn func(abc def: i32) -> u32 {
12165        }
12166    "});
12167
12168    cx.update(|_, cx| {
12169        lsp_store.update(cx, |lsp_store, cx| {
12170            lsp_store
12171                .update_diagnostics(
12172                    LanguageServerId(0),
12173                    lsp::PublishDiagnosticsParams {
12174                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12175                        version: None,
12176                        diagnostics: vec![
12177                            lsp::Diagnostic {
12178                                range: lsp::Range::new(
12179                                    lsp::Position::new(0, 11),
12180                                    lsp::Position::new(0, 12),
12181                                ),
12182                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12183                                ..Default::default()
12184                            },
12185                            lsp::Diagnostic {
12186                                range: lsp::Range::new(
12187                                    lsp::Position::new(0, 12),
12188                                    lsp::Position::new(0, 15),
12189                                ),
12190                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12191                                ..Default::default()
12192                            },
12193                            lsp::Diagnostic {
12194                                range: lsp::Range::new(
12195                                    lsp::Position::new(0, 25),
12196                                    lsp::Position::new(0, 28),
12197                                ),
12198                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12199                                ..Default::default()
12200                            },
12201                        ],
12202                    },
12203                    &[],
12204                    cx,
12205                )
12206                .unwrap()
12207        });
12208    });
12209
12210    executor.run_until_parked();
12211
12212    cx.update_editor(|editor, window, cx| {
12213        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12214    });
12215
12216    cx.assert_editor_state(indoc! {"
12217        fn func(abc def: i32) -> ˇu32 {
12218        }
12219    "});
12220
12221    cx.update_editor(|editor, window, cx| {
12222        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12223    });
12224
12225    cx.assert_editor_state(indoc! {"
12226        fn func(abc ˇdef: i32) -> u32 {
12227        }
12228    "});
12229
12230    cx.update_editor(|editor, window, cx| {
12231        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12232    });
12233
12234    cx.assert_editor_state(indoc! {"
12235        fn func(abcˇ def: i32) -> u32 {
12236        }
12237    "});
12238
12239    cx.update_editor(|editor, window, cx| {
12240        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12241    });
12242
12243    cx.assert_editor_state(indoc! {"
12244        fn func(abc def: i32) -> ˇu32 {
12245        }
12246    "});
12247}
12248
12249#[gpui::test]
12250async fn cycle_through_same_place_diagnostics(
12251    executor: BackgroundExecutor,
12252    cx: &mut TestAppContext,
12253) {
12254    init_test(cx, |_| {});
12255
12256    let mut cx = EditorTestContext::new(cx).await;
12257    let lsp_store =
12258        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12259
12260    cx.set_state(indoc! {"
12261        ˇfn func(abc def: i32) -> u32 {
12262        }
12263    "});
12264
12265    cx.update(|_, cx| {
12266        lsp_store.update(cx, |lsp_store, cx| {
12267            lsp_store
12268                .update_diagnostics(
12269                    LanguageServerId(0),
12270                    lsp::PublishDiagnosticsParams {
12271                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12272                        version: None,
12273                        diagnostics: vec![
12274                            lsp::Diagnostic {
12275                                range: lsp::Range::new(
12276                                    lsp::Position::new(0, 11),
12277                                    lsp::Position::new(0, 12),
12278                                ),
12279                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12280                                ..Default::default()
12281                            },
12282                            lsp::Diagnostic {
12283                                range: lsp::Range::new(
12284                                    lsp::Position::new(0, 12),
12285                                    lsp::Position::new(0, 15),
12286                                ),
12287                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12288                                ..Default::default()
12289                            },
12290                            lsp::Diagnostic {
12291                                range: lsp::Range::new(
12292                                    lsp::Position::new(0, 12),
12293                                    lsp::Position::new(0, 15),
12294                                ),
12295                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12296                                ..Default::default()
12297                            },
12298                            lsp::Diagnostic {
12299                                range: lsp::Range::new(
12300                                    lsp::Position::new(0, 25),
12301                                    lsp::Position::new(0, 28),
12302                                ),
12303                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12304                                ..Default::default()
12305                            },
12306                        ],
12307                    },
12308                    &[],
12309                    cx,
12310                )
12311                .unwrap()
12312        });
12313    });
12314    executor.run_until_parked();
12315
12316    //// Backward
12317
12318    // Fourth diagnostic
12319    cx.update_editor(|editor, window, cx| {
12320        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12321    });
12322    cx.assert_editor_state(indoc! {"
12323        fn func(abc def: i32) -> ˇu32 {
12324        }
12325    "});
12326
12327    // Third diagnostic
12328    cx.update_editor(|editor, window, cx| {
12329        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12330    });
12331    cx.assert_editor_state(indoc! {"
12332        fn func(abc ˇdef: i32) -> u32 {
12333        }
12334    "});
12335
12336    // Second diagnostic, same place
12337    cx.update_editor(|editor, window, cx| {
12338        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12339    });
12340    cx.assert_editor_state(indoc! {"
12341        fn func(abc ˇdef: i32) -> u32 {
12342        }
12343    "});
12344
12345    // First diagnostic
12346    cx.update_editor(|editor, window, cx| {
12347        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12348    });
12349    cx.assert_editor_state(indoc! {"
12350        fn func(abcˇ def: i32) -> u32 {
12351        }
12352    "});
12353
12354    // Wrapped over, fourth diagnostic
12355    cx.update_editor(|editor, window, cx| {
12356        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12357    });
12358    cx.assert_editor_state(indoc! {"
12359        fn func(abc def: i32) -> ˇu32 {
12360        }
12361    "});
12362
12363    cx.update_editor(|editor, window, cx| {
12364        editor.move_to_beginning(&MoveToBeginning, window, cx);
12365    });
12366    cx.assert_editor_state(indoc! {"
12367        ˇfn func(abc def: i32) -> u32 {
12368        }
12369    "});
12370
12371    //// Forward
12372
12373    // First diagnostic
12374    cx.update_editor(|editor, window, cx| {
12375        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12376    });
12377    cx.assert_editor_state(indoc! {"
12378        fn func(abcˇ def: i32) -> u32 {
12379        }
12380    "});
12381
12382    // Second diagnostic
12383    cx.update_editor(|editor, window, cx| {
12384        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12385    });
12386    cx.assert_editor_state(indoc! {"
12387        fn func(abc ˇdef: i32) -> u32 {
12388        }
12389    "});
12390
12391    // Third diagnostic, same place
12392    cx.update_editor(|editor, window, cx| {
12393        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12394    });
12395    cx.assert_editor_state(indoc! {"
12396        fn func(abc ˇdef: i32) -> u32 {
12397        }
12398    "});
12399
12400    // Fourth diagnostic
12401    cx.update_editor(|editor, window, cx| {
12402        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12403    });
12404    cx.assert_editor_state(indoc! {"
12405        fn func(abc def: i32) -> ˇu32 {
12406        }
12407    "});
12408
12409    // Wrapped around, first diagnostic
12410    cx.update_editor(|editor, window, cx| {
12411        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12412    });
12413    cx.assert_editor_state(indoc! {"
12414        fn func(abcˇ def: i32) -> u32 {
12415        }
12416    "});
12417}
12418
12419#[gpui::test]
12420async fn active_diagnostics_dismiss_after_invalidation(
12421    executor: BackgroundExecutor,
12422    cx: &mut TestAppContext,
12423) {
12424    init_test(cx, |_| {});
12425
12426    let mut cx = EditorTestContext::new(cx).await;
12427    let lsp_store =
12428        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12429
12430    cx.set_state(indoc! {"
12431        ˇfn func(abc def: i32) -> u32 {
12432        }
12433    "});
12434
12435    let message = "Something's wrong!";
12436    cx.update(|_, cx| {
12437        lsp_store.update(cx, |lsp_store, cx| {
12438            lsp_store
12439                .update_diagnostics(
12440                    LanguageServerId(0),
12441                    lsp::PublishDiagnosticsParams {
12442                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12443                        version: None,
12444                        diagnostics: vec![lsp::Diagnostic {
12445                            range: lsp::Range::new(
12446                                lsp::Position::new(0, 11),
12447                                lsp::Position::new(0, 12),
12448                            ),
12449                            severity: Some(lsp::DiagnosticSeverity::ERROR),
12450                            message: message.to_string(),
12451                            ..Default::default()
12452                        }],
12453                    },
12454                    &[],
12455                    cx,
12456                )
12457                .unwrap()
12458        });
12459    });
12460    executor.run_until_parked();
12461
12462    cx.update_editor(|editor, window, cx| {
12463        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12464        assert_eq!(
12465            editor
12466                .active_diagnostics
12467                .as_ref()
12468                .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
12469            Some(message),
12470            "Should have a diagnostics group activated"
12471        );
12472    });
12473    cx.assert_editor_state(indoc! {"
12474        fn func(abcˇ def: i32) -> u32 {
12475        }
12476    "});
12477
12478    cx.update(|_, cx| {
12479        lsp_store.update(cx, |lsp_store, cx| {
12480            lsp_store
12481                .update_diagnostics(
12482                    LanguageServerId(0),
12483                    lsp::PublishDiagnosticsParams {
12484                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12485                        version: None,
12486                        diagnostics: Vec::new(),
12487                    },
12488                    &[],
12489                    cx,
12490                )
12491                .unwrap()
12492        });
12493    });
12494    executor.run_until_parked();
12495    cx.update_editor(|editor, _, _| {
12496        assert_eq!(
12497            editor.active_diagnostics, None,
12498            "After no diagnostics set to the editor, no diagnostics should be active"
12499        );
12500    });
12501    cx.assert_editor_state(indoc! {"
12502        fn func(abcˇ def: i32) -> u32 {
12503        }
12504    "});
12505
12506    cx.update_editor(|editor, window, cx| {
12507        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12508        assert_eq!(
12509            editor.active_diagnostics, None,
12510            "Should be no diagnostics to go to and activate"
12511        );
12512    });
12513    cx.assert_editor_state(indoc! {"
12514        fn func(abcˇ def: i32) -> u32 {
12515        }
12516    "});
12517}
12518
12519#[gpui::test]
12520async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12521    init_test(cx, |_| {});
12522
12523    let mut cx = EditorTestContext::new(cx).await;
12524
12525    cx.set_state(indoc! {"
12526        fn func(abˇc def: i32) -> u32 {
12527        }
12528    "});
12529    let lsp_store =
12530        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12531
12532    cx.update(|_, cx| {
12533        lsp_store.update(cx, |lsp_store, cx| {
12534            lsp_store.update_diagnostics(
12535                LanguageServerId(0),
12536                lsp::PublishDiagnosticsParams {
12537                    uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12538                    version: None,
12539                    diagnostics: vec![lsp::Diagnostic {
12540                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12541                        severity: Some(lsp::DiagnosticSeverity::ERROR),
12542                        message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12543                        ..Default::default()
12544                    }],
12545                },
12546                &[],
12547                cx,
12548            )
12549        })
12550    }).unwrap();
12551    cx.run_until_parked();
12552    cx.update_editor(|editor, window, cx| {
12553        hover_popover::hover(editor, &Default::default(), window, cx)
12554    });
12555    cx.run_until_parked();
12556    cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12557}
12558
12559#[gpui::test]
12560async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12561    init_test(cx, |_| {});
12562
12563    let mut cx = EditorTestContext::new(cx).await;
12564
12565    let diff_base = r#"
12566        use some::mod;
12567
12568        const A: u32 = 42;
12569
12570        fn main() {
12571            println!("hello");
12572
12573            println!("world");
12574        }
12575        "#
12576    .unindent();
12577
12578    // Edits are modified, removed, modified, added
12579    cx.set_state(
12580        &r#"
12581        use some::modified;
12582
12583        ˇ
12584        fn main() {
12585            println!("hello there");
12586
12587            println!("around the");
12588            println!("world");
12589        }
12590        "#
12591        .unindent(),
12592    );
12593
12594    cx.set_head_text(&diff_base);
12595    executor.run_until_parked();
12596
12597    cx.update_editor(|editor, window, cx| {
12598        //Wrap around the bottom of the buffer
12599        for _ in 0..3 {
12600            editor.go_to_next_hunk(&GoToHunk, window, cx);
12601        }
12602    });
12603
12604    cx.assert_editor_state(
12605        &r#"
12606        ˇuse some::modified;
12607
12608
12609        fn main() {
12610            println!("hello there");
12611
12612            println!("around the");
12613            println!("world");
12614        }
12615        "#
12616        .unindent(),
12617    );
12618
12619    cx.update_editor(|editor, window, cx| {
12620        //Wrap around the top of the buffer
12621        for _ in 0..2 {
12622            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12623        }
12624    });
12625
12626    cx.assert_editor_state(
12627        &r#"
12628        use some::modified;
12629
12630
12631        fn main() {
12632        ˇ    println!("hello there");
12633
12634            println!("around the");
12635            println!("world");
12636        }
12637        "#
12638        .unindent(),
12639    );
12640
12641    cx.update_editor(|editor, window, cx| {
12642        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12643    });
12644
12645    cx.assert_editor_state(
12646        &r#"
12647        use some::modified;
12648
12649        ˇ
12650        fn main() {
12651            println!("hello there");
12652
12653            println!("around the");
12654            println!("world");
12655        }
12656        "#
12657        .unindent(),
12658    );
12659
12660    cx.update_editor(|editor, window, cx| {
12661        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12662    });
12663
12664    cx.assert_editor_state(
12665        &r#"
12666        ˇuse some::modified;
12667
12668
12669        fn main() {
12670            println!("hello there");
12671
12672            println!("around the");
12673            println!("world");
12674        }
12675        "#
12676        .unindent(),
12677    );
12678
12679    cx.update_editor(|editor, window, cx| {
12680        for _ in 0..2 {
12681            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12682        }
12683    });
12684
12685    cx.assert_editor_state(
12686        &r#"
12687        use some::modified;
12688
12689
12690        fn main() {
12691        ˇ    println!("hello there");
12692
12693            println!("around the");
12694            println!("world");
12695        }
12696        "#
12697        .unindent(),
12698    );
12699
12700    cx.update_editor(|editor, window, cx| {
12701        editor.fold(&Fold, window, cx);
12702    });
12703
12704    cx.update_editor(|editor, window, cx| {
12705        editor.go_to_next_hunk(&GoToHunk, window, cx);
12706    });
12707
12708    cx.assert_editor_state(
12709        &r#"
12710        ˇuse some::modified;
12711
12712
12713        fn main() {
12714            println!("hello there");
12715
12716            println!("around the");
12717            println!("world");
12718        }
12719        "#
12720        .unindent(),
12721    );
12722}
12723
12724#[test]
12725fn test_split_words() {
12726    fn split(text: &str) -> Vec<&str> {
12727        split_words(text).collect()
12728    }
12729
12730    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
12731    assert_eq!(split("hello_world"), &["hello_", "world"]);
12732    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
12733    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
12734    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
12735    assert_eq!(split("helloworld"), &["helloworld"]);
12736
12737    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
12738}
12739
12740#[gpui::test]
12741async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
12742    init_test(cx, |_| {});
12743
12744    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
12745    let mut assert = |before, after| {
12746        let _state_context = cx.set_state(before);
12747        cx.run_until_parked();
12748        cx.update_editor(|editor, window, cx| {
12749            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
12750        });
12751        cx.run_until_parked();
12752        cx.assert_editor_state(after);
12753    };
12754
12755    // Outside bracket jumps to outside of matching bracket
12756    assert("console.logˇ(var);", "console.log(var)ˇ;");
12757    assert("console.log(var)ˇ;", "console.logˇ(var);");
12758
12759    // Inside bracket jumps to inside of matching bracket
12760    assert("console.log(ˇvar);", "console.log(varˇ);");
12761    assert("console.log(varˇ);", "console.log(ˇvar);");
12762
12763    // When outside a bracket and inside, favor jumping to the inside bracket
12764    assert(
12765        "console.log('foo', [1, 2, 3]ˇ);",
12766        "console.log(ˇ'foo', [1, 2, 3]);",
12767    );
12768    assert(
12769        "console.log(ˇ'foo', [1, 2, 3]);",
12770        "console.log('foo', [1, 2, 3]ˇ);",
12771    );
12772
12773    // Bias forward if two options are equally likely
12774    assert(
12775        "let result = curried_fun()ˇ();",
12776        "let result = curried_fun()()ˇ;",
12777    );
12778
12779    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12780    assert(
12781        indoc! {"
12782            function test() {
12783                console.log('test')ˇ
12784            }"},
12785        indoc! {"
12786            function test() {
12787                console.logˇ('test')
12788            }"},
12789    );
12790}
12791
12792#[gpui::test]
12793async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12794    init_test(cx, |_| {});
12795
12796    let fs = FakeFs::new(cx.executor());
12797    fs.insert_tree(
12798        path!("/a"),
12799        json!({
12800            "main.rs": "fn main() { let a = 5; }",
12801            "other.rs": "// Test file",
12802        }),
12803    )
12804    .await;
12805    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12806
12807    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12808    language_registry.add(Arc::new(Language::new(
12809        LanguageConfig {
12810            name: "Rust".into(),
12811            matcher: LanguageMatcher {
12812                path_suffixes: vec!["rs".to_string()],
12813                ..Default::default()
12814            },
12815            brackets: BracketPairConfig {
12816                pairs: vec![BracketPair {
12817                    start: "{".to_string(),
12818                    end: "}".to_string(),
12819                    close: true,
12820                    surround: true,
12821                    newline: true,
12822                }],
12823                disabled_scopes_by_bracket_ix: Vec::new(),
12824            },
12825            ..Default::default()
12826        },
12827        Some(tree_sitter_rust::LANGUAGE.into()),
12828    )));
12829    let mut fake_servers = language_registry.register_fake_lsp(
12830        "Rust",
12831        FakeLspAdapter {
12832            capabilities: lsp::ServerCapabilities {
12833                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12834                    first_trigger_character: "{".to_string(),
12835                    more_trigger_character: None,
12836                }),
12837                ..Default::default()
12838            },
12839            ..Default::default()
12840        },
12841    );
12842
12843    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12844
12845    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12846
12847    let worktree_id = workspace
12848        .update(cx, |workspace, _, cx| {
12849            workspace.project().update(cx, |project, cx| {
12850                project.worktrees(cx).next().unwrap().read(cx).id()
12851            })
12852        })
12853        .unwrap();
12854
12855    let buffer = project
12856        .update(cx, |project, cx| {
12857            project.open_local_buffer(path!("/a/main.rs"), cx)
12858        })
12859        .await
12860        .unwrap();
12861    let editor_handle = workspace
12862        .update(cx, |workspace, window, cx| {
12863            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12864        })
12865        .unwrap()
12866        .await
12867        .unwrap()
12868        .downcast::<Editor>()
12869        .unwrap();
12870
12871    cx.executor().start_waiting();
12872    let fake_server = fake_servers.next().await.unwrap();
12873
12874    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
12875        |params, _| async move {
12876            assert_eq!(
12877                params.text_document_position.text_document.uri,
12878                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12879            );
12880            assert_eq!(
12881                params.text_document_position.position,
12882                lsp::Position::new(0, 21),
12883            );
12884
12885            Ok(Some(vec![lsp::TextEdit {
12886                new_text: "]".to_string(),
12887                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12888            }]))
12889        },
12890    );
12891
12892    editor_handle.update_in(cx, |editor, window, cx| {
12893        window.focus(&editor.focus_handle(cx));
12894        editor.change_selections(None, window, cx, |s| {
12895            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12896        });
12897        editor.handle_input("{", window, cx);
12898    });
12899
12900    cx.executor().run_until_parked();
12901
12902    buffer.update(cx, |buffer, _| {
12903        assert_eq!(
12904            buffer.text(),
12905            "fn main() { let a = {5}; }",
12906            "No extra braces from on type formatting should appear in the buffer"
12907        )
12908    });
12909}
12910
12911#[gpui::test]
12912async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12913    init_test(cx, |_| {});
12914
12915    let fs = FakeFs::new(cx.executor());
12916    fs.insert_tree(
12917        path!("/a"),
12918        json!({
12919            "main.rs": "fn main() { let a = 5; }",
12920            "other.rs": "// Test file",
12921        }),
12922    )
12923    .await;
12924
12925    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12926
12927    let server_restarts = Arc::new(AtomicUsize::new(0));
12928    let closure_restarts = Arc::clone(&server_restarts);
12929    let language_server_name = "test language server";
12930    let language_name: LanguageName = "Rust".into();
12931
12932    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12933    language_registry.add(Arc::new(Language::new(
12934        LanguageConfig {
12935            name: language_name.clone(),
12936            matcher: LanguageMatcher {
12937                path_suffixes: vec!["rs".to_string()],
12938                ..Default::default()
12939            },
12940            ..Default::default()
12941        },
12942        Some(tree_sitter_rust::LANGUAGE.into()),
12943    )));
12944    let mut fake_servers = language_registry.register_fake_lsp(
12945        "Rust",
12946        FakeLspAdapter {
12947            name: language_server_name,
12948            initialization_options: Some(json!({
12949                "testOptionValue": true
12950            })),
12951            initializer: Some(Box::new(move |fake_server| {
12952                let task_restarts = Arc::clone(&closure_restarts);
12953                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
12954                    task_restarts.fetch_add(1, atomic::Ordering::Release);
12955                    futures::future::ready(Ok(()))
12956                });
12957            })),
12958            ..Default::default()
12959        },
12960    );
12961
12962    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12963    let _buffer = project
12964        .update(cx, |project, cx| {
12965            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12966        })
12967        .await
12968        .unwrap();
12969    let _fake_server = fake_servers.next().await.unwrap();
12970    update_test_language_settings(cx, |language_settings| {
12971        language_settings.languages.insert(
12972            language_name.clone(),
12973            LanguageSettingsContent {
12974                tab_size: NonZeroU32::new(8),
12975                ..Default::default()
12976            },
12977        );
12978    });
12979    cx.executor().run_until_parked();
12980    assert_eq!(
12981        server_restarts.load(atomic::Ordering::Acquire),
12982        0,
12983        "Should not restart LSP server on an unrelated change"
12984    );
12985
12986    update_test_project_settings(cx, |project_settings| {
12987        project_settings.lsp.insert(
12988            "Some other server name".into(),
12989            LspSettings {
12990                binary: None,
12991                settings: None,
12992                initialization_options: Some(json!({
12993                    "some other init value": false
12994                })),
12995                enable_lsp_tasks: false,
12996            },
12997        );
12998    });
12999    cx.executor().run_until_parked();
13000    assert_eq!(
13001        server_restarts.load(atomic::Ordering::Acquire),
13002        0,
13003        "Should not restart LSP server on an unrelated LSP settings change"
13004    );
13005
13006    update_test_project_settings(cx, |project_settings| {
13007        project_settings.lsp.insert(
13008            language_server_name.into(),
13009            LspSettings {
13010                binary: None,
13011                settings: None,
13012                initialization_options: Some(json!({
13013                    "anotherInitValue": false
13014                })),
13015                enable_lsp_tasks: false,
13016            },
13017        );
13018    });
13019    cx.executor().run_until_parked();
13020    assert_eq!(
13021        server_restarts.load(atomic::Ordering::Acquire),
13022        1,
13023        "Should restart LSP server on a related LSP settings change"
13024    );
13025
13026    update_test_project_settings(cx, |project_settings| {
13027        project_settings.lsp.insert(
13028            language_server_name.into(),
13029            LspSettings {
13030                binary: None,
13031                settings: None,
13032                initialization_options: Some(json!({
13033                    "anotherInitValue": false
13034                })),
13035                enable_lsp_tasks: false,
13036            },
13037        );
13038    });
13039    cx.executor().run_until_parked();
13040    assert_eq!(
13041        server_restarts.load(atomic::Ordering::Acquire),
13042        1,
13043        "Should not restart LSP server on a related LSP settings change that is the same"
13044    );
13045
13046    update_test_project_settings(cx, |project_settings| {
13047        project_settings.lsp.insert(
13048            language_server_name.into(),
13049            LspSettings {
13050                binary: None,
13051                settings: None,
13052                initialization_options: None,
13053                enable_lsp_tasks: false,
13054            },
13055        );
13056    });
13057    cx.executor().run_until_parked();
13058    assert_eq!(
13059        server_restarts.load(atomic::Ordering::Acquire),
13060        2,
13061        "Should restart LSP server on another related LSP settings change"
13062    );
13063}
13064
13065#[gpui::test]
13066async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13067    init_test(cx, |_| {});
13068
13069    let mut cx = EditorLspTestContext::new_rust(
13070        lsp::ServerCapabilities {
13071            completion_provider: Some(lsp::CompletionOptions {
13072                trigger_characters: Some(vec![".".to_string()]),
13073                resolve_provider: Some(true),
13074                ..Default::default()
13075            }),
13076            ..Default::default()
13077        },
13078        cx,
13079    )
13080    .await;
13081
13082    cx.set_state("fn main() { let a = 2ˇ; }");
13083    cx.simulate_keystroke(".");
13084    let completion_item = lsp::CompletionItem {
13085        label: "some".into(),
13086        kind: Some(lsp::CompletionItemKind::SNIPPET),
13087        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13088        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13089            kind: lsp::MarkupKind::Markdown,
13090            value: "```rust\nSome(2)\n```".to_string(),
13091        })),
13092        deprecated: Some(false),
13093        sort_text: Some("fffffff2".to_string()),
13094        filter_text: Some("some".to_string()),
13095        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13096        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13097            range: lsp::Range {
13098                start: lsp::Position {
13099                    line: 0,
13100                    character: 22,
13101                },
13102                end: lsp::Position {
13103                    line: 0,
13104                    character: 22,
13105                },
13106            },
13107            new_text: "Some(2)".to_string(),
13108        })),
13109        additional_text_edits: Some(vec![lsp::TextEdit {
13110            range: lsp::Range {
13111                start: lsp::Position {
13112                    line: 0,
13113                    character: 20,
13114                },
13115                end: lsp::Position {
13116                    line: 0,
13117                    character: 22,
13118                },
13119            },
13120            new_text: "".to_string(),
13121        }]),
13122        ..Default::default()
13123    };
13124
13125    let closure_completion_item = completion_item.clone();
13126    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13127        let task_completion_item = closure_completion_item.clone();
13128        async move {
13129            Ok(Some(lsp::CompletionResponse::Array(vec![
13130                task_completion_item,
13131            ])))
13132        }
13133    });
13134
13135    request.next().await;
13136
13137    cx.condition(|editor, _| editor.context_menu_visible())
13138        .await;
13139    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13140        editor
13141            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13142            .unwrap()
13143    });
13144    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13145
13146    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13147        let task_completion_item = completion_item.clone();
13148        async move { Ok(task_completion_item) }
13149    })
13150    .next()
13151    .await
13152    .unwrap();
13153    apply_additional_edits.await.unwrap();
13154    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13155}
13156
13157#[gpui::test]
13158async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13159    init_test(cx, |_| {});
13160
13161    let mut cx = EditorLspTestContext::new_rust(
13162        lsp::ServerCapabilities {
13163            completion_provider: Some(lsp::CompletionOptions {
13164                trigger_characters: Some(vec![".".to_string()]),
13165                resolve_provider: Some(true),
13166                ..Default::default()
13167            }),
13168            ..Default::default()
13169        },
13170        cx,
13171    )
13172    .await;
13173
13174    cx.set_state("fn main() { let a = 2ˇ; }");
13175    cx.simulate_keystroke(".");
13176
13177    let item1 = lsp::CompletionItem {
13178        label: "method id()".to_string(),
13179        filter_text: Some("id".to_string()),
13180        detail: None,
13181        documentation: None,
13182        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13183            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13184            new_text: ".id".to_string(),
13185        })),
13186        ..lsp::CompletionItem::default()
13187    };
13188
13189    let item2 = lsp::CompletionItem {
13190        label: "other".to_string(),
13191        filter_text: Some("other".to_string()),
13192        detail: None,
13193        documentation: None,
13194        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13195            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13196            new_text: ".other".to_string(),
13197        })),
13198        ..lsp::CompletionItem::default()
13199    };
13200
13201    let item1 = item1.clone();
13202    cx.set_request_handler::<lsp::request::Completion, _, _>({
13203        let item1 = item1.clone();
13204        move |_, _, _| {
13205            let item1 = item1.clone();
13206            let item2 = item2.clone();
13207            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13208        }
13209    })
13210    .next()
13211    .await;
13212
13213    cx.condition(|editor, _| editor.context_menu_visible())
13214        .await;
13215    cx.update_editor(|editor, _, _| {
13216        let context_menu = editor.context_menu.borrow_mut();
13217        let context_menu = context_menu
13218            .as_ref()
13219            .expect("Should have the context menu deployed");
13220        match context_menu {
13221            CodeContextMenu::Completions(completions_menu) => {
13222                let completions = completions_menu.completions.borrow_mut();
13223                assert_eq!(
13224                    completions
13225                        .iter()
13226                        .map(|completion| &completion.label.text)
13227                        .collect::<Vec<_>>(),
13228                    vec!["method id()", "other"]
13229                )
13230            }
13231            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13232        }
13233    });
13234
13235    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13236        let item1 = item1.clone();
13237        move |_, item_to_resolve, _| {
13238            let item1 = item1.clone();
13239            async move {
13240                if item1 == item_to_resolve {
13241                    Ok(lsp::CompletionItem {
13242                        label: "method id()".to_string(),
13243                        filter_text: Some("id".to_string()),
13244                        detail: Some("Now resolved!".to_string()),
13245                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13246                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13247                            range: lsp::Range::new(
13248                                lsp::Position::new(0, 22),
13249                                lsp::Position::new(0, 22),
13250                            ),
13251                            new_text: ".id".to_string(),
13252                        })),
13253                        ..lsp::CompletionItem::default()
13254                    })
13255                } else {
13256                    Ok(item_to_resolve)
13257                }
13258            }
13259        }
13260    })
13261    .next()
13262    .await
13263    .unwrap();
13264    cx.run_until_parked();
13265
13266    cx.update_editor(|editor, window, cx| {
13267        editor.context_menu_next(&Default::default(), window, cx);
13268    });
13269
13270    cx.update_editor(|editor, _, _| {
13271        let context_menu = editor.context_menu.borrow_mut();
13272        let context_menu = context_menu
13273            .as_ref()
13274            .expect("Should have the context menu deployed");
13275        match context_menu {
13276            CodeContextMenu::Completions(completions_menu) => {
13277                let completions = completions_menu.completions.borrow_mut();
13278                assert_eq!(
13279                    completions
13280                        .iter()
13281                        .map(|completion| &completion.label.text)
13282                        .collect::<Vec<_>>(),
13283                    vec!["method id() Now resolved!", "other"],
13284                    "Should update first completion label, but not second as the filter text did not match."
13285                );
13286            }
13287            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13288        }
13289    });
13290}
13291
13292#[gpui::test]
13293async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13294    init_test(cx, |_| {});
13295
13296    let mut cx = EditorLspTestContext::new_rust(
13297        lsp::ServerCapabilities {
13298            completion_provider: Some(lsp::CompletionOptions {
13299                trigger_characters: Some(vec![".".to_string()]),
13300                resolve_provider: Some(true),
13301                ..Default::default()
13302            }),
13303            ..Default::default()
13304        },
13305        cx,
13306    )
13307    .await;
13308
13309    cx.set_state("fn main() { let a = 2ˇ; }");
13310    cx.simulate_keystroke(".");
13311
13312    let unresolved_item_1 = lsp::CompletionItem {
13313        label: "id".to_string(),
13314        filter_text: Some("id".to_string()),
13315        detail: None,
13316        documentation: None,
13317        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13318            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13319            new_text: ".id".to_string(),
13320        })),
13321        ..lsp::CompletionItem::default()
13322    };
13323    let resolved_item_1 = lsp::CompletionItem {
13324        additional_text_edits: Some(vec![lsp::TextEdit {
13325            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13326            new_text: "!!".to_string(),
13327        }]),
13328        ..unresolved_item_1.clone()
13329    };
13330    let unresolved_item_2 = lsp::CompletionItem {
13331        label: "other".to_string(),
13332        filter_text: Some("other".to_string()),
13333        detail: None,
13334        documentation: None,
13335        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13336            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13337            new_text: ".other".to_string(),
13338        })),
13339        ..lsp::CompletionItem::default()
13340    };
13341    let resolved_item_2 = lsp::CompletionItem {
13342        additional_text_edits: Some(vec![lsp::TextEdit {
13343            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13344            new_text: "??".to_string(),
13345        }]),
13346        ..unresolved_item_2.clone()
13347    };
13348
13349    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13350    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13351    cx.lsp
13352        .server
13353        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13354            let unresolved_item_1 = unresolved_item_1.clone();
13355            let resolved_item_1 = resolved_item_1.clone();
13356            let unresolved_item_2 = unresolved_item_2.clone();
13357            let resolved_item_2 = resolved_item_2.clone();
13358            let resolve_requests_1 = resolve_requests_1.clone();
13359            let resolve_requests_2 = resolve_requests_2.clone();
13360            move |unresolved_request, _| {
13361                let unresolved_item_1 = unresolved_item_1.clone();
13362                let resolved_item_1 = resolved_item_1.clone();
13363                let unresolved_item_2 = unresolved_item_2.clone();
13364                let resolved_item_2 = resolved_item_2.clone();
13365                let resolve_requests_1 = resolve_requests_1.clone();
13366                let resolve_requests_2 = resolve_requests_2.clone();
13367                async move {
13368                    if unresolved_request == unresolved_item_1 {
13369                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13370                        Ok(resolved_item_1.clone())
13371                    } else if unresolved_request == unresolved_item_2 {
13372                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13373                        Ok(resolved_item_2.clone())
13374                    } else {
13375                        panic!("Unexpected completion item {unresolved_request:?}")
13376                    }
13377                }
13378            }
13379        })
13380        .detach();
13381
13382    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13383        let unresolved_item_1 = unresolved_item_1.clone();
13384        let unresolved_item_2 = unresolved_item_2.clone();
13385        async move {
13386            Ok(Some(lsp::CompletionResponse::Array(vec![
13387                unresolved_item_1,
13388                unresolved_item_2,
13389            ])))
13390        }
13391    })
13392    .next()
13393    .await;
13394
13395    cx.condition(|editor, _| editor.context_menu_visible())
13396        .await;
13397    cx.update_editor(|editor, _, _| {
13398        let context_menu = editor.context_menu.borrow_mut();
13399        let context_menu = context_menu
13400            .as_ref()
13401            .expect("Should have the context menu deployed");
13402        match context_menu {
13403            CodeContextMenu::Completions(completions_menu) => {
13404                let completions = completions_menu.completions.borrow_mut();
13405                assert_eq!(
13406                    completions
13407                        .iter()
13408                        .map(|completion| &completion.label.text)
13409                        .collect::<Vec<_>>(),
13410                    vec!["id", "other"]
13411                )
13412            }
13413            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13414        }
13415    });
13416    cx.run_until_parked();
13417
13418    cx.update_editor(|editor, window, cx| {
13419        editor.context_menu_next(&ContextMenuNext, window, cx);
13420    });
13421    cx.run_until_parked();
13422    cx.update_editor(|editor, window, cx| {
13423        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13424    });
13425    cx.run_until_parked();
13426    cx.update_editor(|editor, window, cx| {
13427        editor.context_menu_next(&ContextMenuNext, window, cx);
13428    });
13429    cx.run_until_parked();
13430    cx.update_editor(|editor, window, cx| {
13431        editor
13432            .compose_completion(&ComposeCompletion::default(), window, cx)
13433            .expect("No task returned")
13434    })
13435    .await
13436    .expect("Completion failed");
13437    cx.run_until_parked();
13438
13439    cx.update_editor(|editor, _, cx| {
13440        assert_eq!(
13441            resolve_requests_1.load(atomic::Ordering::Acquire),
13442            1,
13443            "Should always resolve once despite multiple selections"
13444        );
13445        assert_eq!(
13446            resolve_requests_2.load(atomic::Ordering::Acquire),
13447            1,
13448            "Should always resolve once after multiple selections and applying the completion"
13449        );
13450        assert_eq!(
13451            editor.text(cx),
13452            "fn main() { let a = ??.other; }",
13453            "Should use resolved data when applying the completion"
13454        );
13455    });
13456}
13457
13458#[gpui::test]
13459async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13460    init_test(cx, |_| {});
13461
13462    let item_0 = lsp::CompletionItem {
13463        label: "abs".into(),
13464        insert_text: Some("abs".into()),
13465        data: Some(json!({ "very": "special"})),
13466        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13467        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13468            lsp::InsertReplaceEdit {
13469                new_text: "abs".to_string(),
13470                insert: lsp::Range::default(),
13471                replace: lsp::Range::default(),
13472            },
13473        )),
13474        ..lsp::CompletionItem::default()
13475    };
13476    let items = iter::once(item_0.clone())
13477        .chain((11..51).map(|i| lsp::CompletionItem {
13478            label: format!("item_{}", i),
13479            insert_text: Some(format!("item_{}", i)),
13480            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13481            ..lsp::CompletionItem::default()
13482        }))
13483        .collect::<Vec<_>>();
13484
13485    let default_commit_characters = vec!["?".to_string()];
13486    let default_data = json!({ "default": "data"});
13487    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13488    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13489    let default_edit_range = lsp::Range {
13490        start: lsp::Position {
13491            line: 0,
13492            character: 5,
13493        },
13494        end: lsp::Position {
13495            line: 0,
13496            character: 5,
13497        },
13498    };
13499
13500    let mut cx = EditorLspTestContext::new_rust(
13501        lsp::ServerCapabilities {
13502            completion_provider: Some(lsp::CompletionOptions {
13503                trigger_characters: Some(vec![".".to_string()]),
13504                resolve_provider: Some(true),
13505                ..Default::default()
13506            }),
13507            ..Default::default()
13508        },
13509        cx,
13510    )
13511    .await;
13512
13513    cx.set_state("fn main() { let a = 2ˇ; }");
13514    cx.simulate_keystroke(".");
13515
13516    let completion_data = default_data.clone();
13517    let completion_characters = default_commit_characters.clone();
13518    let completion_items = items.clone();
13519    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13520        let default_data = completion_data.clone();
13521        let default_commit_characters = completion_characters.clone();
13522        let items = completion_items.clone();
13523        async move {
13524            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13525                items,
13526                item_defaults: Some(lsp::CompletionListItemDefaults {
13527                    data: Some(default_data.clone()),
13528                    commit_characters: Some(default_commit_characters.clone()),
13529                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13530                        default_edit_range,
13531                    )),
13532                    insert_text_format: Some(default_insert_text_format),
13533                    insert_text_mode: Some(default_insert_text_mode),
13534                }),
13535                ..lsp::CompletionList::default()
13536            })))
13537        }
13538    })
13539    .next()
13540    .await;
13541
13542    let resolved_items = Arc::new(Mutex::new(Vec::new()));
13543    cx.lsp
13544        .server
13545        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13546            let closure_resolved_items = resolved_items.clone();
13547            move |item_to_resolve, _| {
13548                let closure_resolved_items = closure_resolved_items.clone();
13549                async move {
13550                    closure_resolved_items.lock().push(item_to_resolve.clone());
13551                    Ok(item_to_resolve)
13552                }
13553            }
13554        })
13555        .detach();
13556
13557    cx.condition(|editor, _| editor.context_menu_visible())
13558        .await;
13559    cx.run_until_parked();
13560    cx.update_editor(|editor, _, _| {
13561        let menu = editor.context_menu.borrow_mut();
13562        match menu.as_ref().expect("should have the completions menu") {
13563            CodeContextMenu::Completions(completions_menu) => {
13564                assert_eq!(
13565                    completions_menu
13566                        .entries
13567                        .borrow()
13568                        .iter()
13569                        .map(|mat| mat.string.clone())
13570                        .collect::<Vec<String>>(),
13571                    items
13572                        .iter()
13573                        .map(|completion| completion.label.clone())
13574                        .collect::<Vec<String>>()
13575                );
13576            }
13577            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13578        }
13579    });
13580    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13581    // with 4 from the end.
13582    assert_eq!(
13583        *resolved_items.lock(),
13584        [&items[0..16], &items[items.len() - 4..items.len()]]
13585            .concat()
13586            .iter()
13587            .cloned()
13588            .map(|mut item| {
13589                if item.data.is_none() {
13590                    item.data = Some(default_data.clone());
13591                }
13592                item
13593            })
13594            .collect::<Vec<lsp::CompletionItem>>(),
13595        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13596    );
13597    resolved_items.lock().clear();
13598
13599    cx.update_editor(|editor, window, cx| {
13600        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13601    });
13602    cx.run_until_parked();
13603    // Completions that have already been resolved are skipped.
13604    assert_eq!(
13605        *resolved_items.lock(),
13606        items[items.len() - 16..items.len() - 4]
13607            .iter()
13608            .cloned()
13609            .map(|mut item| {
13610                if item.data.is_none() {
13611                    item.data = Some(default_data.clone());
13612                }
13613                item
13614            })
13615            .collect::<Vec<lsp::CompletionItem>>()
13616    );
13617    resolved_items.lock().clear();
13618}
13619
13620#[gpui::test]
13621async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13622    init_test(cx, |_| {});
13623
13624    let mut cx = EditorLspTestContext::new(
13625        Language::new(
13626            LanguageConfig {
13627                matcher: LanguageMatcher {
13628                    path_suffixes: vec!["jsx".into()],
13629                    ..Default::default()
13630                },
13631                overrides: [(
13632                    "element".into(),
13633                    LanguageConfigOverride {
13634                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
13635                        ..Default::default()
13636                    },
13637                )]
13638                .into_iter()
13639                .collect(),
13640                ..Default::default()
13641            },
13642            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13643        )
13644        .with_override_query("(jsx_self_closing_element) @element")
13645        .unwrap(),
13646        lsp::ServerCapabilities {
13647            completion_provider: Some(lsp::CompletionOptions {
13648                trigger_characters: Some(vec![":".to_string()]),
13649                ..Default::default()
13650            }),
13651            ..Default::default()
13652        },
13653        cx,
13654    )
13655    .await;
13656
13657    cx.lsp
13658        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13659            Ok(Some(lsp::CompletionResponse::Array(vec![
13660                lsp::CompletionItem {
13661                    label: "bg-blue".into(),
13662                    ..Default::default()
13663                },
13664                lsp::CompletionItem {
13665                    label: "bg-red".into(),
13666                    ..Default::default()
13667                },
13668                lsp::CompletionItem {
13669                    label: "bg-yellow".into(),
13670                    ..Default::default()
13671                },
13672            ])))
13673        });
13674
13675    cx.set_state(r#"<p class="bgˇ" />"#);
13676
13677    // Trigger completion when typing a dash, because the dash is an extra
13678    // word character in the 'element' scope, which contains the cursor.
13679    cx.simulate_keystroke("-");
13680    cx.executor().run_until_parked();
13681    cx.update_editor(|editor, _, _| {
13682        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13683        {
13684            assert_eq!(
13685                completion_menu_entries(&menu),
13686                &["bg-red", "bg-blue", "bg-yellow"]
13687            );
13688        } else {
13689            panic!("expected completion menu to be open");
13690        }
13691    });
13692
13693    cx.simulate_keystroke("l");
13694    cx.executor().run_until_parked();
13695    cx.update_editor(|editor, _, _| {
13696        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13697        {
13698            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
13699        } else {
13700            panic!("expected completion menu to be open");
13701        }
13702    });
13703
13704    // When filtering completions, consider the character after the '-' to
13705    // be the start of a subword.
13706    cx.set_state(r#"<p class="yelˇ" />"#);
13707    cx.simulate_keystroke("l");
13708    cx.executor().run_until_parked();
13709    cx.update_editor(|editor, _, _| {
13710        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13711        {
13712            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
13713        } else {
13714            panic!("expected completion menu to be open");
13715        }
13716    });
13717}
13718
13719fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
13720    let entries = menu.entries.borrow();
13721    entries.iter().map(|mat| mat.string.clone()).collect()
13722}
13723
13724#[gpui::test]
13725async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
13726    init_test(cx, |settings| {
13727        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
13728            FormatterList(vec![Formatter::Prettier].into()),
13729        ))
13730    });
13731
13732    let fs = FakeFs::new(cx.executor());
13733    fs.insert_file(path!("/file.ts"), Default::default()).await;
13734
13735    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
13736    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13737
13738    language_registry.add(Arc::new(Language::new(
13739        LanguageConfig {
13740            name: "TypeScript".into(),
13741            matcher: LanguageMatcher {
13742                path_suffixes: vec!["ts".to_string()],
13743                ..Default::default()
13744            },
13745            ..Default::default()
13746        },
13747        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13748    )));
13749    update_test_language_settings(cx, |settings| {
13750        settings.defaults.prettier = Some(PrettierSettings {
13751            allowed: true,
13752            ..PrettierSettings::default()
13753        });
13754    });
13755
13756    let test_plugin = "test_plugin";
13757    let _ = language_registry.register_fake_lsp(
13758        "TypeScript",
13759        FakeLspAdapter {
13760            prettier_plugins: vec![test_plugin],
13761            ..Default::default()
13762        },
13763    );
13764
13765    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13766    let buffer = project
13767        .update(cx, |project, cx| {
13768            project.open_local_buffer(path!("/file.ts"), cx)
13769        })
13770        .await
13771        .unwrap();
13772
13773    let buffer_text = "one\ntwo\nthree\n";
13774    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13775    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13776    editor.update_in(cx, |editor, window, cx| {
13777        editor.set_text(buffer_text, window, cx)
13778    });
13779
13780    editor
13781        .update_in(cx, |editor, window, cx| {
13782            editor.perform_format(
13783                project.clone(),
13784                FormatTrigger::Manual,
13785                FormatTarget::Buffers,
13786                window,
13787                cx,
13788            )
13789        })
13790        .unwrap()
13791        .await;
13792    assert_eq!(
13793        editor.update(cx, |editor, cx| editor.text(cx)),
13794        buffer_text.to_string() + prettier_format_suffix,
13795        "Test prettier formatting was not applied to the original buffer text",
13796    );
13797
13798    update_test_language_settings(cx, |settings| {
13799        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13800    });
13801    let format = editor.update_in(cx, |editor, window, cx| {
13802        editor.perform_format(
13803            project.clone(),
13804            FormatTrigger::Manual,
13805            FormatTarget::Buffers,
13806            window,
13807            cx,
13808        )
13809    });
13810    format.await.unwrap();
13811    assert_eq!(
13812        editor.update(cx, |editor, cx| editor.text(cx)),
13813        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13814        "Autoformatting (via test prettier) was not applied to the original buffer text",
13815    );
13816}
13817
13818#[gpui::test]
13819async fn test_addition_reverts(cx: &mut TestAppContext) {
13820    init_test(cx, |_| {});
13821    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13822    let base_text = indoc! {r#"
13823        struct Row;
13824        struct Row1;
13825        struct Row2;
13826
13827        struct Row4;
13828        struct Row5;
13829        struct Row6;
13830
13831        struct Row8;
13832        struct Row9;
13833        struct Row10;"#};
13834
13835    // When addition hunks are not adjacent to carets, no hunk revert is performed
13836    assert_hunk_revert(
13837        indoc! {r#"struct Row;
13838                   struct Row1;
13839                   struct Row1.1;
13840                   struct Row1.2;
13841                   struct Row2;ˇ
13842
13843                   struct Row4;
13844                   struct Row5;
13845                   struct Row6;
13846
13847                   struct Row8;
13848                   ˇstruct Row9;
13849                   struct Row9.1;
13850                   struct Row9.2;
13851                   struct Row9.3;
13852                   struct Row10;"#},
13853        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13854        indoc! {r#"struct Row;
13855                   struct Row1;
13856                   struct Row1.1;
13857                   struct Row1.2;
13858                   struct Row2;ˇ
13859
13860                   struct Row4;
13861                   struct Row5;
13862                   struct Row6;
13863
13864                   struct Row8;
13865                   ˇstruct Row9;
13866                   struct Row9.1;
13867                   struct Row9.2;
13868                   struct Row9.3;
13869                   struct Row10;"#},
13870        base_text,
13871        &mut cx,
13872    );
13873    // Same for selections
13874    assert_hunk_revert(
13875        indoc! {r#"struct Row;
13876                   struct Row1;
13877                   struct Row2;
13878                   struct Row2.1;
13879                   struct Row2.2;
13880                   «ˇ
13881                   struct Row4;
13882                   struct» Row5;
13883                   «struct Row6;
13884                   ˇ»
13885                   struct Row9.1;
13886                   struct Row9.2;
13887                   struct Row9.3;
13888                   struct Row8;
13889                   struct Row9;
13890                   struct Row10;"#},
13891        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13892        indoc! {r#"struct Row;
13893                   struct Row1;
13894                   struct Row2;
13895                   struct Row2.1;
13896                   struct Row2.2;
13897                   «ˇ
13898                   struct Row4;
13899                   struct» Row5;
13900                   «struct Row6;
13901                   ˇ»
13902                   struct Row9.1;
13903                   struct Row9.2;
13904                   struct Row9.3;
13905                   struct Row8;
13906                   struct Row9;
13907                   struct Row10;"#},
13908        base_text,
13909        &mut cx,
13910    );
13911
13912    // When carets and selections intersect the addition hunks, those are reverted.
13913    // Adjacent carets got merged.
13914    assert_hunk_revert(
13915        indoc! {r#"struct Row;
13916                   ˇ// something on the top
13917                   struct Row1;
13918                   struct Row2;
13919                   struct Roˇw3.1;
13920                   struct Row2.2;
13921                   struct Row2.3;ˇ
13922
13923                   struct Row4;
13924                   struct ˇRow5.1;
13925                   struct Row5.2;
13926                   struct «Rowˇ»5.3;
13927                   struct Row5;
13928                   struct Row6;
13929                   ˇ
13930                   struct Row9.1;
13931                   struct «Rowˇ»9.2;
13932                   struct «ˇRow»9.3;
13933                   struct Row8;
13934                   struct Row9;
13935                   «ˇ// something on bottom»
13936                   struct Row10;"#},
13937        vec![
13938            DiffHunkStatusKind::Added,
13939            DiffHunkStatusKind::Added,
13940            DiffHunkStatusKind::Added,
13941            DiffHunkStatusKind::Added,
13942            DiffHunkStatusKind::Added,
13943        ],
13944        indoc! {r#"struct Row;
13945                   ˇstruct Row1;
13946                   struct Row2;
13947                   ˇ
13948                   struct Row4;
13949                   ˇstruct Row5;
13950                   struct Row6;
13951                   ˇ
13952                   ˇstruct Row8;
13953                   struct Row9;
13954                   ˇstruct Row10;"#},
13955        base_text,
13956        &mut cx,
13957    );
13958}
13959
13960#[gpui::test]
13961async fn test_modification_reverts(cx: &mut TestAppContext) {
13962    init_test(cx, |_| {});
13963    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13964    let base_text = indoc! {r#"
13965        struct Row;
13966        struct Row1;
13967        struct Row2;
13968
13969        struct Row4;
13970        struct Row5;
13971        struct Row6;
13972
13973        struct Row8;
13974        struct Row9;
13975        struct Row10;"#};
13976
13977    // Modification hunks behave the same as the addition ones.
13978    assert_hunk_revert(
13979        indoc! {r#"struct Row;
13980                   struct Row1;
13981                   struct Row33;
13982                   ˇ
13983                   struct Row4;
13984                   struct Row5;
13985                   struct Row6;
13986                   ˇ
13987                   struct Row99;
13988                   struct Row9;
13989                   struct Row10;"#},
13990        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13991        indoc! {r#"struct Row;
13992                   struct Row1;
13993                   struct Row33;
13994                   ˇ
13995                   struct Row4;
13996                   struct Row5;
13997                   struct Row6;
13998                   ˇ
13999                   struct Row99;
14000                   struct Row9;
14001                   struct Row10;"#},
14002        base_text,
14003        &mut cx,
14004    );
14005    assert_hunk_revert(
14006        indoc! {r#"struct Row;
14007                   struct Row1;
14008                   struct Row33;
14009                   «ˇ
14010                   struct Row4;
14011                   struct» Row5;
14012                   «struct Row6;
14013                   ˇ»
14014                   struct Row99;
14015                   struct Row9;
14016                   struct Row10;"#},
14017        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14018        indoc! {r#"struct Row;
14019                   struct Row1;
14020                   struct Row33;
14021                   «ˇ
14022                   struct Row4;
14023                   struct» Row5;
14024                   «struct Row6;
14025                   ˇ»
14026                   struct Row99;
14027                   struct Row9;
14028                   struct Row10;"#},
14029        base_text,
14030        &mut cx,
14031    );
14032
14033    assert_hunk_revert(
14034        indoc! {r#"ˇstruct Row1.1;
14035                   struct Row1;
14036                   «ˇstr»uct Row22;
14037
14038                   struct ˇRow44;
14039                   struct Row5;
14040                   struct «Rˇ»ow66;ˇ
14041
14042                   «struˇ»ct Row88;
14043                   struct Row9;
14044                   struct Row1011;ˇ"#},
14045        vec![
14046            DiffHunkStatusKind::Modified,
14047            DiffHunkStatusKind::Modified,
14048            DiffHunkStatusKind::Modified,
14049            DiffHunkStatusKind::Modified,
14050            DiffHunkStatusKind::Modified,
14051            DiffHunkStatusKind::Modified,
14052        ],
14053        indoc! {r#"struct Row;
14054                   ˇstruct Row1;
14055                   struct Row2;
14056                   ˇ
14057                   struct Row4;
14058                   ˇstruct Row5;
14059                   struct Row6;
14060                   ˇ
14061                   struct Row8;
14062                   ˇstruct Row9;
14063                   struct Row10;ˇ"#},
14064        base_text,
14065        &mut cx,
14066    );
14067}
14068
14069#[gpui::test]
14070async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14071    init_test(cx, |_| {});
14072    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14073    let base_text = indoc! {r#"
14074        one
14075
14076        two
14077        three
14078        "#};
14079
14080    cx.set_head_text(base_text);
14081    cx.set_state("\nˇ\n");
14082    cx.executor().run_until_parked();
14083    cx.update_editor(|editor, _window, cx| {
14084        editor.expand_selected_diff_hunks(cx);
14085    });
14086    cx.executor().run_until_parked();
14087    cx.update_editor(|editor, window, cx| {
14088        editor.backspace(&Default::default(), window, cx);
14089    });
14090    cx.run_until_parked();
14091    cx.assert_state_with_diff(
14092        indoc! {r#"
14093
14094        - two
14095        - threeˇ
14096        +
14097        "#}
14098        .to_string(),
14099    );
14100}
14101
14102#[gpui::test]
14103async fn test_deletion_reverts(cx: &mut TestAppContext) {
14104    init_test(cx, |_| {});
14105    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14106    let base_text = indoc! {r#"struct Row;
14107struct Row1;
14108struct Row2;
14109
14110struct Row4;
14111struct Row5;
14112struct Row6;
14113
14114struct Row8;
14115struct Row9;
14116struct Row10;"#};
14117
14118    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14119    assert_hunk_revert(
14120        indoc! {r#"struct Row;
14121                   struct Row2;
14122
14123                   ˇstruct Row4;
14124                   struct Row5;
14125                   struct Row6;
14126                   ˇ
14127                   struct Row8;
14128                   struct Row10;"#},
14129        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14130        indoc! {r#"struct Row;
14131                   struct Row2;
14132
14133                   ˇstruct Row4;
14134                   struct Row5;
14135                   struct Row6;
14136                   ˇ
14137                   struct Row8;
14138                   struct Row10;"#},
14139        base_text,
14140        &mut cx,
14141    );
14142    assert_hunk_revert(
14143        indoc! {r#"struct Row;
14144                   struct Row2;
14145
14146                   «ˇstruct Row4;
14147                   struct» Row5;
14148                   «struct Row6;
14149                   ˇ»
14150                   struct Row8;
14151                   struct Row10;"#},
14152        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14153        indoc! {r#"struct Row;
14154                   struct Row2;
14155
14156                   «ˇstruct Row4;
14157                   struct» Row5;
14158                   «struct Row6;
14159                   ˇ»
14160                   struct Row8;
14161                   struct Row10;"#},
14162        base_text,
14163        &mut cx,
14164    );
14165
14166    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14167    assert_hunk_revert(
14168        indoc! {r#"struct Row;
14169                   ˇstruct Row2;
14170
14171                   struct Row4;
14172                   struct Row5;
14173                   struct Row6;
14174
14175                   struct Row8;ˇ
14176                   struct Row10;"#},
14177        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14178        indoc! {r#"struct Row;
14179                   struct Row1;
14180                   ˇstruct Row2;
14181
14182                   struct Row4;
14183                   struct Row5;
14184                   struct Row6;
14185
14186                   struct Row8;ˇ
14187                   struct Row9;
14188                   struct Row10;"#},
14189        base_text,
14190        &mut cx,
14191    );
14192    assert_hunk_revert(
14193        indoc! {r#"struct Row;
14194                   struct Row2«ˇ;
14195                   struct Row4;
14196                   struct» Row5;
14197                   «struct Row6;
14198
14199                   struct Row8;ˇ»
14200                   struct Row10;"#},
14201        vec![
14202            DiffHunkStatusKind::Deleted,
14203            DiffHunkStatusKind::Deleted,
14204            DiffHunkStatusKind::Deleted,
14205        ],
14206        indoc! {r#"struct Row;
14207                   struct Row1;
14208                   struct Row2«ˇ;
14209
14210                   struct Row4;
14211                   struct» Row5;
14212                   «struct Row6;
14213
14214                   struct Row8;ˇ»
14215                   struct Row9;
14216                   struct Row10;"#},
14217        base_text,
14218        &mut cx,
14219    );
14220}
14221
14222#[gpui::test]
14223async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14224    init_test(cx, |_| {});
14225
14226    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14227    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14228    let base_text_3 =
14229        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14230
14231    let text_1 = edit_first_char_of_every_line(base_text_1);
14232    let text_2 = edit_first_char_of_every_line(base_text_2);
14233    let text_3 = edit_first_char_of_every_line(base_text_3);
14234
14235    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14236    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14237    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14238
14239    let multibuffer = cx.new(|cx| {
14240        let mut multibuffer = MultiBuffer::new(ReadWrite);
14241        multibuffer.push_excerpts(
14242            buffer_1.clone(),
14243            [
14244                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14245                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14246                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14247            ],
14248            cx,
14249        );
14250        multibuffer.push_excerpts(
14251            buffer_2.clone(),
14252            [
14253                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14254                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14255                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14256            ],
14257            cx,
14258        );
14259        multibuffer.push_excerpts(
14260            buffer_3.clone(),
14261            [
14262                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14263                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14264                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14265            ],
14266            cx,
14267        );
14268        multibuffer
14269    });
14270
14271    let fs = FakeFs::new(cx.executor());
14272    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14273    let (editor, cx) = cx
14274        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14275    editor.update_in(cx, |editor, _window, cx| {
14276        for (buffer, diff_base) in [
14277            (buffer_1.clone(), base_text_1),
14278            (buffer_2.clone(), base_text_2),
14279            (buffer_3.clone(), base_text_3),
14280        ] {
14281            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14282            editor
14283                .buffer
14284                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14285        }
14286    });
14287    cx.executor().run_until_parked();
14288
14289    editor.update_in(cx, |editor, window, cx| {
14290        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}");
14291        editor.select_all(&SelectAll, window, cx);
14292        editor.git_restore(&Default::default(), window, cx);
14293    });
14294    cx.executor().run_until_parked();
14295
14296    // When all ranges are selected, all buffer hunks are reverted.
14297    editor.update(cx, |editor, cx| {
14298        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");
14299    });
14300    buffer_1.update(cx, |buffer, _| {
14301        assert_eq!(buffer.text(), base_text_1);
14302    });
14303    buffer_2.update(cx, |buffer, _| {
14304        assert_eq!(buffer.text(), base_text_2);
14305    });
14306    buffer_3.update(cx, |buffer, _| {
14307        assert_eq!(buffer.text(), base_text_3);
14308    });
14309
14310    editor.update_in(cx, |editor, window, cx| {
14311        editor.undo(&Default::default(), window, cx);
14312    });
14313
14314    editor.update_in(cx, |editor, window, cx| {
14315        editor.change_selections(None, window, cx, |s| {
14316            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14317        });
14318        editor.git_restore(&Default::default(), window, cx);
14319    });
14320
14321    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14322    // but not affect buffer_2 and its related excerpts.
14323    editor.update(cx, |editor, cx| {
14324        assert_eq!(
14325            editor.text(cx),
14326            "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}"
14327        );
14328    });
14329    buffer_1.update(cx, |buffer, _| {
14330        assert_eq!(buffer.text(), base_text_1);
14331    });
14332    buffer_2.update(cx, |buffer, _| {
14333        assert_eq!(
14334            buffer.text(),
14335            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14336        );
14337    });
14338    buffer_3.update(cx, |buffer, _| {
14339        assert_eq!(
14340            buffer.text(),
14341            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14342        );
14343    });
14344
14345    fn edit_first_char_of_every_line(text: &str) -> String {
14346        text.split('\n')
14347            .map(|line| format!("X{}", &line[1..]))
14348            .collect::<Vec<_>>()
14349            .join("\n")
14350    }
14351}
14352
14353#[gpui::test]
14354async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14355    init_test(cx, |_| {});
14356
14357    let cols = 4;
14358    let rows = 10;
14359    let sample_text_1 = sample_text(rows, cols, 'a');
14360    assert_eq!(
14361        sample_text_1,
14362        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14363    );
14364    let sample_text_2 = sample_text(rows, cols, 'l');
14365    assert_eq!(
14366        sample_text_2,
14367        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14368    );
14369    let sample_text_3 = sample_text(rows, cols, 'v');
14370    assert_eq!(
14371        sample_text_3,
14372        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14373    );
14374
14375    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14376    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14377    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14378
14379    let multi_buffer = cx.new(|cx| {
14380        let mut multibuffer = MultiBuffer::new(ReadWrite);
14381        multibuffer.push_excerpts(
14382            buffer_1.clone(),
14383            [
14384                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14385                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14386                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14387            ],
14388            cx,
14389        );
14390        multibuffer.push_excerpts(
14391            buffer_2.clone(),
14392            [
14393                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14394                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14395                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14396            ],
14397            cx,
14398        );
14399        multibuffer.push_excerpts(
14400            buffer_3.clone(),
14401            [
14402                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14403                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14404                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14405            ],
14406            cx,
14407        );
14408        multibuffer
14409    });
14410
14411    let fs = FakeFs::new(cx.executor());
14412    fs.insert_tree(
14413        "/a",
14414        json!({
14415            "main.rs": sample_text_1,
14416            "other.rs": sample_text_2,
14417            "lib.rs": sample_text_3,
14418        }),
14419    )
14420    .await;
14421    let project = Project::test(fs, ["/a".as_ref()], cx).await;
14422    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14423    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14424    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14425        Editor::new(
14426            EditorMode::Full,
14427            multi_buffer,
14428            Some(project.clone()),
14429            window,
14430            cx,
14431        )
14432    });
14433    let multibuffer_item_id = workspace
14434        .update(cx, |workspace, window, cx| {
14435            assert!(
14436                workspace.active_item(cx).is_none(),
14437                "active item should be None before the first item is added"
14438            );
14439            workspace.add_item_to_active_pane(
14440                Box::new(multi_buffer_editor.clone()),
14441                None,
14442                true,
14443                window,
14444                cx,
14445            );
14446            let active_item = workspace
14447                .active_item(cx)
14448                .expect("should have an active item after adding the multi buffer");
14449            assert!(
14450                !active_item.is_singleton(cx),
14451                "A multi buffer was expected to active after adding"
14452            );
14453            active_item.item_id()
14454        })
14455        .unwrap();
14456    cx.executor().run_until_parked();
14457
14458    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14459        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14460            s.select_ranges(Some(1..2))
14461        });
14462        editor.open_excerpts(&OpenExcerpts, window, cx);
14463    });
14464    cx.executor().run_until_parked();
14465    let first_item_id = workspace
14466        .update(cx, |workspace, window, cx| {
14467            let active_item = workspace
14468                .active_item(cx)
14469                .expect("should have an active item after navigating into the 1st buffer");
14470            let first_item_id = active_item.item_id();
14471            assert_ne!(
14472                first_item_id, multibuffer_item_id,
14473                "Should navigate into the 1st buffer and activate it"
14474            );
14475            assert!(
14476                active_item.is_singleton(cx),
14477                "New active item should be a singleton buffer"
14478            );
14479            assert_eq!(
14480                active_item
14481                    .act_as::<Editor>(cx)
14482                    .expect("should have navigated into an editor for the 1st buffer")
14483                    .read(cx)
14484                    .text(cx),
14485                sample_text_1
14486            );
14487
14488            workspace
14489                .go_back(workspace.active_pane().downgrade(), window, cx)
14490                .detach_and_log_err(cx);
14491
14492            first_item_id
14493        })
14494        .unwrap();
14495    cx.executor().run_until_parked();
14496    workspace
14497        .update(cx, |workspace, _, cx| {
14498            let active_item = workspace
14499                .active_item(cx)
14500                .expect("should have an active item after navigating back");
14501            assert_eq!(
14502                active_item.item_id(),
14503                multibuffer_item_id,
14504                "Should navigate back to the multi buffer"
14505            );
14506            assert!(!active_item.is_singleton(cx));
14507        })
14508        .unwrap();
14509
14510    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14511        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14512            s.select_ranges(Some(39..40))
14513        });
14514        editor.open_excerpts(&OpenExcerpts, window, cx);
14515    });
14516    cx.executor().run_until_parked();
14517    let second_item_id = workspace
14518        .update(cx, |workspace, window, cx| {
14519            let active_item = workspace
14520                .active_item(cx)
14521                .expect("should have an active item after navigating into the 2nd buffer");
14522            let second_item_id = active_item.item_id();
14523            assert_ne!(
14524                second_item_id, multibuffer_item_id,
14525                "Should navigate away from the multibuffer"
14526            );
14527            assert_ne!(
14528                second_item_id, first_item_id,
14529                "Should navigate into the 2nd buffer and activate it"
14530            );
14531            assert!(
14532                active_item.is_singleton(cx),
14533                "New active item should be a singleton buffer"
14534            );
14535            assert_eq!(
14536                active_item
14537                    .act_as::<Editor>(cx)
14538                    .expect("should have navigated into an editor")
14539                    .read(cx)
14540                    .text(cx),
14541                sample_text_2
14542            );
14543
14544            workspace
14545                .go_back(workspace.active_pane().downgrade(), window, cx)
14546                .detach_and_log_err(cx);
14547
14548            second_item_id
14549        })
14550        .unwrap();
14551    cx.executor().run_until_parked();
14552    workspace
14553        .update(cx, |workspace, _, cx| {
14554            let active_item = workspace
14555                .active_item(cx)
14556                .expect("should have an active item after navigating back from the 2nd buffer");
14557            assert_eq!(
14558                active_item.item_id(),
14559                multibuffer_item_id,
14560                "Should navigate back from the 2nd buffer to the multi buffer"
14561            );
14562            assert!(!active_item.is_singleton(cx));
14563        })
14564        .unwrap();
14565
14566    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14567        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14568            s.select_ranges(Some(70..70))
14569        });
14570        editor.open_excerpts(&OpenExcerpts, window, cx);
14571    });
14572    cx.executor().run_until_parked();
14573    workspace
14574        .update(cx, |workspace, window, cx| {
14575            let active_item = workspace
14576                .active_item(cx)
14577                .expect("should have an active item after navigating into the 3rd buffer");
14578            let third_item_id = active_item.item_id();
14579            assert_ne!(
14580                third_item_id, multibuffer_item_id,
14581                "Should navigate into the 3rd buffer and activate it"
14582            );
14583            assert_ne!(third_item_id, first_item_id);
14584            assert_ne!(third_item_id, second_item_id);
14585            assert!(
14586                active_item.is_singleton(cx),
14587                "New active item should be a singleton buffer"
14588            );
14589            assert_eq!(
14590                active_item
14591                    .act_as::<Editor>(cx)
14592                    .expect("should have navigated into an editor")
14593                    .read(cx)
14594                    .text(cx),
14595                sample_text_3
14596            );
14597
14598            workspace
14599                .go_back(workspace.active_pane().downgrade(), window, cx)
14600                .detach_and_log_err(cx);
14601        })
14602        .unwrap();
14603    cx.executor().run_until_parked();
14604    workspace
14605        .update(cx, |workspace, _, cx| {
14606            let active_item = workspace
14607                .active_item(cx)
14608                .expect("should have an active item after navigating back from the 3rd buffer");
14609            assert_eq!(
14610                active_item.item_id(),
14611                multibuffer_item_id,
14612                "Should navigate back from the 3rd buffer to the multi buffer"
14613            );
14614            assert!(!active_item.is_singleton(cx));
14615        })
14616        .unwrap();
14617}
14618
14619#[gpui::test]
14620async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14621    init_test(cx, |_| {});
14622
14623    let mut cx = EditorTestContext::new(cx).await;
14624
14625    let diff_base = r#"
14626        use some::mod;
14627
14628        const A: u32 = 42;
14629
14630        fn main() {
14631            println!("hello");
14632
14633            println!("world");
14634        }
14635        "#
14636    .unindent();
14637
14638    cx.set_state(
14639        &r#"
14640        use some::modified;
14641
14642        ˇ
14643        fn main() {
14644            println!("hello there");
14645
14646            println!("around the");
14647            println!("world");
14648        }
14649        "#
14650        .unindent(),
14651    );
14652
14653    cx.set_head_text(&diff_base);
14654    executor.run_until_parked();
14655
14656    cx.update_editor(|editor, window, cx| {
14657        editor.go_to_next_hunk(&GoToHunk, window, cx);
14658        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14659    });
14660    executor.run_until_parked();
14661    cx.assert_state_with_diff(
14662        r#"
14663          use some::modified;
14664
14665
14666          fn main() {
14667        -     println!("hello");
14668        + ˇ    println!("hello there");
14669
14670              println!("around the");
14671              println!("world");
14672          }
14673        "#
14674        .unindent(),
14675    );
14676
14677    cx.update_editor(|editor, window, cx| {
14678        for _ in 0..2 {
14679            editor.go_to_next_hunk(&GoToHunk, window, cx);
14680            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14681        }
14682    });
14683    executor.run_until_parked();
14684    cx.assert_state_with_diff(
14685        r#"
14686        - use some::mod;
14687        + ˇuse some::modified;
14688
14689
14690          fn main() {
14691        -     println!("hello");
14692        +     println!("hello there");
14693
14694        +     println!("around the");
14695              println!("world");
14696          }
14697        "#
14698        .unindent(),
14699    );
14700
14701    cx.update_editor(|editor, window, cx| {
14702        editor.go_to_next_hunk(&GoToHunk, window, cx);
14703        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14704    });
14705    executor.run_until_parked();
14706    cx.assert_state_with_diff(
14707        r#"
14708        - use some::mod;
14709        + use some::modified;
14710
14711        - const A: u32 = 42;
14712          ˇ
14713          fn main() {
14714        -     println!("hello");
14715        +     println!("hello there");
14716
14717        +     println!("around the");
14718              println!("world");
14719          }
14720        "#
14721        .unindent(),
14722    );
14723
14724    cx.update_editor(|editor, window, cx| {
14725        editor.cancel(&Cancel, window, cx);
14726    });
14727
14728    cx.assert_state_with_diff(
14729        r#"
14730          use some::modified;
14731
14732          ˇ
14733          fn main() {
14734              println!("hello there");
14735
14736              println!("around the");
14737              println!("world");
14738          }
14739        "#
14740        .unindent(),
14741    );
14742}
14743
14744#[gpui::test]
14745async fn test_diff_base_change_with_expanded_diff_hunks(
14746    executor: BackgroundExecutor,
14747    cx: &mut TestAppContext,
14748) {
14749    init_test(cx, |_| {});
14750
14751    let mut cx = EditorTestContext::new(cx).await;
14752
14753    let diff_base = r#"
14754        use some::mod1;
14755        use some::mod2;
14756
14757        const A: u32 = 42;
14758        const B: u32 = 42;
14759        const C: u32 = 42;
14760
14761        fn main() {
14762            println!("hello");
14763
14764            println!("world");
14765        }
14766        "#
14767    .unindent();
14768
14769    cx.set_state(
14770        &r#"
14771        use some::mod2;
14772
14773        const A: u32 = 42;
14774        const C: u32 = 42;
14775
14776        fn main(ˇ) {
14777            //println!("hello");
14778
14779            println!("world");
14780            //
14781            //
14782        }
14783        "#
14784        .unindent(),
14785    );
14786
14787    cx.set_head_text(&diff_base);
14788    executor.run_until_parked();
14789
14790    cx.update_editor(|editor, window, cx| {
14791        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14792    });
14793    executor.run_until_parked();
14794    cx.assert_state_with_diff(
14795        r#"
14796        - use some::mod1;
14797          use some::mod2;
14798
14799          const A: u32 = 42;
14800        - const B: u32 = 42;
14801          const C: u32 = 42;
14802
14803          fn main(ˇ) {
14804        -     println!("hello");
14805        +     //println!("hello");
14806
14807              println!("world");
14808        +     //
14809        +     //
14810          }
14811        "#
14812        .unindent(),
14813    );
14814
14815    cx.set_head_text("new diff base!");
14816    executor.run_until_parked();
14817    cx.assert_state_with_diff(
14818        r#"
14819        - new diff base!
14820        + use some::mod2;
14821        +
14822        + const A: u32 = 42;
14823        + const C: u32 = 42;
14824        +
14825        + fn main(ˇ) {
14826        +     //println!("hello");
14827        +
14828        +     println!("world");
14829        +     //
14830        +     //
14831        + }
14832        "#
14833        .unindent(),
14834    );
14835}
14836
14837#[gpui::test]
14838async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14839    init_test(cx, |_| {});
14840
14841    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14842    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14843    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14844    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14845    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14846    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14847
14848    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14849    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14850    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14851
14852    let multi_buffer = cx.new(|cx| {
14853        let mut multibuffer = MultiBuffer::new(ReadWrite);
14854        multibuffer.push_excerpts(
14855            buffer_1.clone(),
14856            [
14857                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14858                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14859                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14860            ],
14861            cx,
14862        );
14863        multibuffer.push_excerpts(
14864            buffer_2.clone(),
14865            [
14866                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14867                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14868                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14869            ],
14870            cx,
14871        );
14872        multibuffer.push_excerpts(
14873            buffer_3.clone(),
14874            [
14875                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14876                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14877                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14878            ],
14879            cx,
14880        );
14881        multibuffer
14882    });
14883
14884    let editor =
14885        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14886    editor
14887        .update(cx, |editor, _window, cx| {
14888            for (buffer, diff_base) in [
14889                (buffer_1.clone(), file_1_old),
14890                (buffer_2.clone(), file_2_old),
14891                (buffer_3.clone(), file_3_old),
14892            ] {
14893                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14894                editor
14895                    .buffer
14896                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14897            }
14898        })
14899        .unwrap();
14900
14901    let mut cx = EditorTestContext::for_editor(editor, cx).await;
14902    cx.run_until_parked();
14903
14904    cx.assert_editor_state(
14905        &"
14906            ˇaaa
14907            ccc
14908            ddd
14909
14910            ggg
14911            hhh
14912
14913
14914            lll
14915            mmm
14916            NNN
14917
14918            qqq
14919            rrr
14920
14921            uuu
14922            111
14923            222
14924            333
14925
14926            666
14927            777
14928
14929            000
14930            !!!"
14931        .unindent(),
14932    );
14933
14934    cx.update_editor(|editor, window, cx| {
14935        editor.select_all(&SelectAll, window, cx);
14936        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14937    });
14938    cx.executor().run_until_parked();
14939
14940    cx.assert_state_with_diff(
14941        "
14942            «aaa
14943          - bbb
14944            ccc
14945            ddd
14946
14947            ggg
14948            hhh
14949
14950
14951            lll
14952            mmm
14953          - nnn
14954          + NNN
14955
14956            qqq
14957            rrr
14958
14959            uuu
14960            111
14961            222
14962            333
14963
14964          + 666
14965            777
14966
14967            000
14968            !!!ˇ»"
14969            .unindent(),
14970    );
14971}
14972
14973#[gpui::test]
14974async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14975    init_test(cx, |_| {});
14976
14977    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14978    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14979
14980    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14981    let multi_buffer = cx.new(|cx| {
14982        let mut multibuffer = MultiBuffer::new(ReadWrite);
14983        multibuffer.push_excerpts(
14984            buffer.clone(),
14985            [
14986                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
14987                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
14988                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
14989            ],
14990            cx,
14991        );
14992        multibuffer
14993    });
14994
14995    let editor =
14996        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14997    editor
14998        .update(cx, |editor, _window, cx| {
14999            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15000            editor
15001                .buffer
15002                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15003        })
15004        .unwrap();
15005
15006    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15007    cx.run_until_parked();
15008
15009    cx.update_editor(|editor, window, cx| {
15010        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15011    });
15012    cx.executor().run_until_parked();
15013
15014    // When the start of a hunk coincides with the start of its excerpt,
15015    // the hunk is expanded. When the start of a a hunk is earlier than
15016    // the start of its excerpt, the hunk is not expanded.
15017    cx.assert_state_with_diff(
15018        "
15019            ˇaaa
15020          - bbb
15021          + BBB
15022
15023          - ddd
15024          - eee
15025          + DDD
15026          + EEE
15027            fff
15028
15029            iii
15030        "
15031        .unindent(),
15032    );
15033}
15034
15035#[gpui::test]
15036async fn test_edits_around_expanded_insertion_hunks(
15037    executor: BackgroundExecutor,
15038    cx: &mut TestAppContext,
15039) {
15040    init_test(cx, |_| {});
15041
15042    let mut cx = EditorTestContext::new(cx).await;
15043
15044    let diff_base = r#"
15045        use some::mod1;
15046        use some::mod2;
15047
15048        const A: u32 = 42;
15049
15050        fn main() {
15051            println!("hello");
15052
15053            println!("world");
15054        }
15055        "#
15056    .unindent();
15057    executor.run_until_parked();
15058    cx.set_state(
15059        &r#"
15060        use some::mod1;
15061        use some::mod2;
15062
15063        const A: u32 = 42;
15064        const B: u32 = 42;
15065        const C: u32 = 42;
15066        ˇ
15067
15068        fn main() {
15069            println!("hello");
15070
15071            println!("world");
15072        }
15073        "#
15074        .unindent(),
15075    );
15076
15077    cx.set_head_text(&diff_base);
15078    executor.run_until_parked();
15079
15080    cx.update_editor(|editor, window, cx| {
15081        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15082    });
15083    executor.run_until_parked();
15084
15085    cx.assert_state_with_diff(
15086        r#"
15087        use some::mod1;
15088        use some::mod2;
15089
15090        const A: u32 = 42;
15091      + const B: u32 = 42;
15092      + const C: u32 = 42;
15093      + ˇ
15094
15095        fn main() {
15096            println!("hello");
15097
15098            println!("world");
15099        }
15100      "#
15101        .unindent(),
15102    );
15103
15104    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15105    executor.run_until_parked();
15106
15107    cx.assert_state_with_diff(
15108        r#"
15109        use some::mod1;
15110        use some::mod2;
15111
15112        const A: u32 = 42;
15113      + const B: u32 = 42;
15114      + const C: u32 = 42;
15115      + const D: u32 = 42;
15116      + ˇ
15117
15118        fn main() {
15119            println!("hello");
15120
15121            println!("world");
15122        }
15123      "#
15124        .unindent(),
15125    );
15126
15127    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15128    executor.run_until_parked();
15129
15130    cx.assert_state_with_diff(
15131        r#"
15132        use some::mod1;
15133        use some::mod2;
15134
15135        const A: u32 = 42;
15136      + const B: u32 = 42;
15137      + const C: u32 = 42;
15138      + const D: u32 = 42;
15139      + const E: u32 = 42;
15140      + ˇ
15141
15142        fn main() {
15143            println!("hello");
15144
15145            println!("world");
15146        }
15147      "#
15148        .unindent(),
15149    );
15150
15151    cx.update_editor(|editor, window, cx| {
15152        editor.delete_line(&DeleteLine, window, cx);
15153    });
15154    executor.run_until_parked();
15155
15156    cx.assert_state_with_diff(
15157        r#"
15158        use some::mod1;
15159        use some::mod2;
15160
15161        const A: u32 = 42;
15162      + const B: u32 = 42;
15163      + const C: u32 = 42;
15164      + const D: u32 = 42;
15165      + const E: u32 = 42;
15166        ˇ
15167        fn main() {
15168            println!("hello");
15169
15170            println!("world");
15171        }
15172      "#
15173        .unindent(),
15174    );
15175
15176    cx.update_editor(|editor, window, cx| {
15177        editor.move_up(&MoveUp, window, cx);
15178        editor.delete_line(&DeleteLine, window, cx);
15179        editor.move_up(&MoveUp, window, cx);
15180        editor.delete_line(&DeleteLine, window, cx);
15181        editor.move_up(&MoveUp, window, cx);
15182        editor.delete_line(&DeleteLine, window, cx);
15183    });
15184    executor.run_until_parked();
15185    cx.assert_state_with_diff(
15186        r#"
15187        use some::mod1;
15188        use some::mod2;
15189
15190        const A: u32 = 42;
15191      + const B: u32 = 42;
15192        ˇ
15193        fn main() {
15194            println!("hello");
15195
15196            println!("world");
15197        }
15198      "#
15199        .unindent(),
15200    );
15201
15202    cx.update_editor(|editor, window, cx| {
15203        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15204        editor.delete_line(&DeleteLine, window, cx);
15205    });
15206    executor.run_until_parked();
15207    cx.assert_state_with_diff(
15208        r#"
15209        ˇ
15210        fn main() {
15211            println!("hello");
15212
15213            println!("world");
15214        }
15215      "#
15216        .unindent(),
15217    );
15218}
15219
15220#[gpui::test]
15221async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15222    init_test(cx, |_| {});
15223
15224    let mut cx = EditorTestContext::new(cx).await;
15225    cx.set_head_text(indoc! { "
15226        one
15227        two
15228        three
15229        four
15230        five
15231        "
15232    });
15233    cx.set_state(indoc! { "
15234        one
15235        ˇthree
15236        five
15237    "});
15238    cx.run_until_parked();
15239    cx.update_editor(|editor, window, cx| {
15240        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15241    });
15242    cx.assert_state_with_diff(
15243        indoc! { "
15244        one
15245      - two
15246        ˇthree
15247      - four
15248        five
15249    "}
15250        .to_string(),
15251    );
15252    cx.update_editor(|editor, window, cx| {
15253        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15254    });
15255
15256    cx.assert_state_with_diff(
15257        indoc! { "
15258        one
15259        ˇthree
15260        five
15261    "}
15262        .to_string(),
15263    );
15264
15265    cx.set_state(indoc! { "
15266        one
15267        ˇTWO
15268        three
15269        four
15270        five
15271    "});
15272    cx.run_until_parked();
15273    cx.update_editor(|editor, window, cx| {
15274        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15275    });
15276
15277    cx.assert_state_with_diff(
15278        indoc! { "
15279            one
15280          - two
15281          + ˇTWO
15282            three
15283            four
15284            five
15285        "}
15286        .to_string(),
15287    );
15288    cx.update_editor(|editor, window, cx| {
15289        editor.move_up(&Default::default(), window, cx);
15290        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15291    });
15292    cx.assert_state_with_diff(
15293        indoc! { "
15294            one
15295            ˇTWO
15296            three
15297            four
15298            five
15299        "}
15300        .to_string(),
15301    );
15302}
15303
15304#[gpui::test]
15305async fn test_edits_around_expanded_deletion_hunks(
15306    executor: BackgroundExecutor,
15307    cx: &mut TestAppContext,
15308) {
15309    init_test(cx, |_| {});
15310
15311    let mut cx = EditorTestContext::new(cx).await;
15312
15313    let diff_base = r#"
15314        use some::mod1;
15315        use some::mod2;
15316
15317        const A: u32 = 42;
15318        const B: u32 = 42;
15319        const C: u32 = 42;
15320
15321
15322        fn main() {
15323            println!("hello");
15324
15325            println!("world");
15326        }
15327    "#
15328    .unindent();
15329    executor.run_until_parked();
15330    cx.set_state(
15331        &r#"
15332        use some::mod1;
15333        use some::mod2;
15334
15335        ˇconst B: u32 = 42;
15336        const C: u32 = 42;
15337
15338
15339        fn main() {
15340            println!("hello");
15341
15342            println!("world");
15343        }
15344        "#
15345        .unindent(),
15346    );
15347
15348    cx.set_head_text(&diff_base);
15349    executor.run_until_parked();
15350
15351    cx.update_editor(|editor, window, cx| {
15352        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15353    });
15354    executor.run_until_parked();
15355
15356    cx.assert_state_with_diff(
15357        r#"
15358        use some::mod1;
15359        use some::mod2;
15360
15361      - const A: u32 = 42;
15362        ˇconst B: u32 = 42;
15363        const C: u32 = 42;
15364
15365
15366        fn main() {
15367            println!("hello");
15368
15369            println!("world");
15370        }
15371      "#
15372        .unindent(),
15373    );
15374
15375    cx.update_editor(|editor, window, cx| {
15376        editor.delete_line(&DeleteLine, window, cx);
15377    });
15378    executor.run_until_parked();
15379    cx.assert_state_with_diff(
15380        r#"
15381        use some::mod1;
15382        use some::mod2;
15383
15384      - const A: u32 = 42;
15385      - const B: u32 = 42;
15386        ˇconst C: u32 = 42;
15387
15388
15389        fn main() {
15390            println!("hello");
15391
15392            println!("world");
15393        }
15394      "#
15395        .unindent(),
15396    );
15397
15398    cx.update_editor(|editor, window, cx| {
15399        editor.delete_line(&DeleteLine, window, cx);
15400    });
15401    executor.run_until_parked();
15402    cx.assert_state_with_diff(
15403        r#"
15404        use some::mod1;
15405        use some::mod2;
15406
15407      - const A: u32 = 42;
15408      - const B: u32 = 42;
15409      - const C: u32 = 42;
15410        ˇ
15411
15412        fn main() {
15413            println!("hello");
15414
15415            println!("world");
15416        }
15417      "#
15418        .unindent(),
15419    );
15420
15421    cx.update_editor(|editor, window, cx| {
15422        editor.handle_input("replacement", window, cx);
15423    });
15424    executor.run_until_parked();
15425    cx.assert_state_with_diff(
15426        r#"
15427        use some::mod1;
15428        use some::mod2;
15429
15430      - const A: u32 = 42;
15431      - const B: u32 = 42;
15432      - const C: u32 = 42;
15433      -
15434      + replacementˇ
15435
15436        fn main() {
15437            println!("hello");
15438
15439            println!("world");
15440        }
15441      "#
15442        .unindent(),
15443    );
15444}
15445
15446#[gpui::test]
15447async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15448    init_test(cx, |_| {});
15449
15450    let mut cx = EditorTestContext::new(cx).await;
15451
15452    let base_text = r#"
15453        one
15454        two
15455        three
15456        four
15457        five
15458    "#
15459    .unindent();
15460    executor.run_until_parked();
15461    cx.set_state(
15462        &r#"
15463        one
15464        two
15465        fˇour
15466        five
15467        "#
15468        .unindent(),
15469    );
15470
15471    cx.set_head_text(&base_text);
15472    executor.run_until_parked();
15473
15474    cx.update_editor(|editor, window, cx| {
15475        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15476    });
15477    executor.run_until_parked();
15478
15479    cx.assert_state_with_diff(
15480        r#"
15481          one
15482          two
15483        - three
15484          fˇour
15485          five
15486        "#
15487        .unindent(),
15488    );
15489
15490    cx.update_editor(|editor, window, cx| {
15491        editor.backspace(&Backspace, window, cx);
15492        editor.backspace(&Backspace, window, cx);
15493    });
15494    executor.run_until_parked();
15495    cx.assert_state_with_diff(
15496        r#"
15497          one
15498          two
15499        - threeˇ
15500        - four
15501        + our
15502          five
15503        "#
15504        .unindent(),
15505    );
15506}
15507
15508#[gpui::test]
15509async fn test_edit_after_expanded_modification_hunk(
15510    executor: BackgroundExecutor,
15511    cx: &mut TestAppContext,
15512) {
15513    init_test(cx, |_| {});
15514
15515    let mut cx = EditorTestContext::new(cx).await;
15516
15517    let diff_base = r#"
15518        use some::mod1;
15519        use some::mod2;
15520
15521        const A: u32 = 42;
15522        const B: u32 = 42;
15523        const C: u32 = 42;
15524        const D: u32 = 42;
15525
15526
15527        fn main() {
15528            println!("hello");
15529
15530            println!("world");
15531        }"#
15532    .unindent();
15533
15534    cx.set_state(
15535        &r#"
15536        use some::mod1;
15537        use some::mod2;
15538
15539        const A: u32 = 42;
15540        const B: u32 = 42;
15541        const C: u32 = 43ˇ
15542        const D: u32 = 42;
15543
15544
15545        fn main() {
15546            println!("hello");
15547
15548            println!("world");
15549        }"#
15550        .unindent(),
15551    );
15552
15553    cx.set_head_text(&diff_base);
15554    executor.run_until_parked();
15555    cx.update_editor(|editor, window, cx| {
15556        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15557    });
15558    executor.run_until_parked();
15559
15560    cx.assert_state_with_diff(
15561        r#"
15562        use some::mod1;
15563        use some::mod2;
15564
15565        const A: u32 = 42;
15566        const B: u32 = 42;
15567      - const C: u32 = 42;
15568      + const C: u32 = 43ˇ
15569        const D: u32 = 42;
15570
15571
15572        fn main() {
15573            println!("hello");
15574
15575            println!("world");
15576        }"#
15577        .unindent(),
15578    );
15579
15580    cx.update_editor(|editor, window, cx| {
15581        editor.handle_input("\nnew_line\n", window, cx);
15582    });
15583    executor.run_until_parked();
15584
15585    cx.assert_state_with_diff(
15586        r#"
15587        use some::mod1;
15588        use some::mod2;
15589
15590        const A: u32 = 42;
15591        const B: u32 = 42;
15592      - const C: u32 = 42;
15593      + const C: u32 = 43
15594      + new_line
15595      + ˇ
15596        const D: u32 = 42;
15597
15598
15599        fn main() {
15600            println!("hello");
15601
15602            println!("world");
15603        }"#
15604        .unindent(),
15605    );
15606}
15607
15608#[gpui::test]
15609async fn test_stage_and_unstage_added_file_hunk(
15610    executor: BackgroundExecutor,
15611    cx: &mut TestAppContext,
15612) {
15613    init_test(cx, |_| {});
15614
15615    let mut cx = EditorTestContext::new(cx).await;
15616    cx.update_editor(|editor, _, cx| {
15617        editor.set_expand_all_diff_hunks(cx);
15618    });
15619
15620    let working_copy = r#"
15621            ˇfn main() {
15622                println!("hello, world!");
15623            }
15624        "#
15625    .unindent();
15626
15627    cx.set_state(&working_copy);
15628    executor.run_until_parked();
15629
15630    cx.assert_state_with_diff(
15631        r#"
15632            + ˇfn main() {
15633            +     println!("hello, world!");
15634            + }
15635        "#
15636        .unindent(),
15637    );
15638    cx.assert_index_text(None);
15639
15640    cx.update_editor(|editor, window, cx| {
15641        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15642    });
15643    executor.run_until_parked();
15644    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15645    cx.assert_state_with_diff(
15646        r#"
15647            + ˇfn main() {
15648            +     println!("hello, world!");
15649            + }
15650        "#
15651        .unindent(),
15652    );
15653
15654    cx.update_editor(|editor, window, cx| {
15655        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15656    });
15657    executor.run_until_parked();
15658    cx.assert_index_text(None);
15659}
15660
15661async fn setup_indent_guides_editor(
15662    text: &str,
15663    cx: &mut TestAppContext,
15664) -> (BufferId, EditorTestContext) {
15665    init_test(cx, |_| {});
15666
15667    let mut cx = EditorTestContext::new(cx).await;
15668
15669    let buffer_id = cx.update_editor(|editor, window, cx| {
15670        editor.set_text(text, window, cx);
15671        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15672
15673        buffer_ids[0]
15674    });
15675
15676    (buffer_id, cx)
15677}
15678
15679fn assert_indent_guides(
15680    range: Range<u32>,
15681    expected: Vec<IndentGuide>,
15682    active_indices: Option<Vec<usize>>,
15683    cx: &mut EditorTestContext,
15684) {
15685    let indent_guides = cx.update_editor(|editor, window, cx| {
15686        let snapshot = editor.snapshot(window, cx).display_snapshot;
15687        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15688            editor,
15689            MultiBufferRow(range.start)..MultiBufferRow(range.end),
15690            true,
15691            &snapshot,
15692            cx,
15693        );
15694
15695        indent_guides.sort_by(|a, b| {
15696            a.depth.cmp(&b.depth).then(
15697                a.start_row
15698                    .cmp(&b.start_row)
15699                    .then(a.end_row.cmp(&b.end_row)),
15700            )
15701        });
15702        indent_guides
15703    });
15704
15705    if let Some(expected) = active_indices {
15706        let active_indices = cx.update_editor(|editor, window, cx| {
15707            let snapshot = editor.snapshot(window, cx).display_snapshot;
15708            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15709        });
15710
15711        assert_eq!(
15712            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15713            expected,
15714            "Active indent guide indices do not match"
15715        );
15716    }
15717
15718    assert_eq!(indent_guides, expected, "Indent guides do not match");
15719}
15720
15721fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15722    IndentGuide {
15723        buffer_id,
15724        start_row: MultiBufferRow(start_row),
15725        end_row: MultiBufferRow(end_row),
15726        depth,
15727        tab_size: 4,
15728        settings: IndentGuideSettings {
15729            enabled: true,
15730            line_width: 1,
15731            active_line_width: 1,
15732            ..Default::default()
15733        },
15734    }
15735}
15736
15737#[gpui::test]
15738async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15739    let (buffer_id, mut cx) = setup_indent_guides_editor(
15740        &"
15741    fn main() {
15742        let a = 1;
15743    }"
15744        .unindent(),
15745        cx,
15746    )
15747    .await;
15748
15749    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15750}
15751
15752#[gpui::test]
15753async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15754    let (buffer_id, mut cx) = setup_indent_guides_editor(
15755        &"
15756    fn main() {
15757        let a = 1;
15758        let b = 2;
15759    }"
15760        .unindent(),
15761        cx,
15762    )
15763    .await;
15764
15765    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15766}
15767
15768#[gpui::test]
15769async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15770    let (buffer_id, mut cx) = setup_indent_guides_editor(
15771        &"
15772    fn main() {
15773        let a = 1;
15774        if a == 3 {
15775            let b = 2;
15776        } else {
15777            let c = 3;
15778        }
15779    }"
15780        .unindent(),
15781        cx,
15782    )
15783    .await;
15784
15785    assert_indent_guides(
15786        0..8,
15787        vec![
15788            indent_guide(buffer_id, 1, 6, 0),
15789            indent_guide(buffer_id, 3, 3, 1),
15790            indent_guide(buffer_id, 5, 5, 1),
15791        ],
15792        None,
15793        &mut cx,
15794    );
15795}
15796
15797#[gpui::test]
15798async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15799    let (buffer_id, mut cx) = setup_indent_guides_editor(
15800        &"
15801    fn main() {
15802        let a = 1;
15803            let b = 2;
15804        let c = 3;
15805    }"
15806        .unindent(),
15807        cx,
15808    )
15809    .await;
15810
15811    assert_indent_guides(
15812        0..5,
15813        vec![
15814            indent_guide(buffer_id, 1, 3, 0),
15815            indent_guide(buffer_id, 2, 2, 1),
15816        ],
15817        None,
15818        &mut cx,
15819    );
15820}
15821
15822#[gpui::test]
15823async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15824    let (buffer_id, mut cx) = setup_indent_guides_editor(
15825        &"
15826        fn main() {
15827            let a = 1;
15828
15829            let c = 3;
15830        }"
15831        .unindent(),
15832        cx,
15833    )
15834    .await;
15835
15836    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15837}
15838
15839#[gpui::test]
15840async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15841    let (buffer_id, mut cx) = setup_indent_guides_editor(
15842        &"
15843        fn main() {
15844            let a = 1;
15845
15846            let c = 3;
15847
15848            if a == 3 {
15849                let b = 2;
15850            } else {
15851                let c = 3;
15852            }
15853        }"
15854        .unindent(),
15855        cx,
15856    )
15857    .await;
15858
15859    assert_indent_guides(
15860        0..11,
15861        vec![
15862            indent_guide(buffer_id, 1, 9, 0),
15863            indent_guide(buffer_id, 6, 6, 1),
15864            indent_guide(buffer_id, 8, 8, 1),
15865        ],
15866        None,
15867        &mut cx,
15868    );
15869}
15870
15871#[gpui::test]
15872async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15873    let (buffer_id, mut cx) = setup_indent_guides_editor(
15874        &"
15875        fn main() {
15876            let a = 1;
15877
15878            let c = 3;
15879
15880            if a == 3 {
15881                let b = 2;
15882            } else {
15883                let c = 3;
15884            }
15885        }"
15886        .unindent(),
15887        cx,
15888    )
15889    .await;
15890
15891    assert_indent_guides(
15892        1..11,
15893        vec![
15894            indent_guide(buffer_id, 1, 9, 0),
15895            indent_guide(buffer_id, 6, 6, 1),
15896            indent_guide(buffer_id, 8, 8, 1),
15897        ],
15898        None,
15899        &mut cx,
15900    );
15901}
15902
15903#[gpui::test]
15904async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15905    let (buffer_id, mut cx) = setup_indent_guides_editor(
15906        &"
15907        fn main() {
15908            let a = 1;
15909
15910            let c = 3;
15911
15912            if a == 3 {
15913                let b = 2;
15914            } else {
15915                let c = 3;
15916            }
15917        }"
15918        .unindent(),
15919        cx,
15920    )
15921    .await;
15922
15923    assert_indent_guides(
15924        1..10,
15925        vec![
15926            indent_guide(buffer_id, 1, 9, 0),
15927            indent_guide(buffer_id, 6, 6, 1),
15928            indent_guide(buffer_id, 8, 8, 1),
15929        ],
15930        None,
15931        &mut cx,
15932    );
15933}
15934
15935#[gpui::test]
15936async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15937    let (buffer_id, mut cx) = setup_indent_guides_editor(
15938        &"
15939        block1
15940            block2
15941                block3
15942                    block4
15943            block2
15944        block1
15945        block1"
15946            .unindent(),
15947        cx,
15948    )
15949    .await;
15950
15951    assert_indent_guides(
15952        1..10,
15953        vec![
15954            indent_guide(buffer_id, 1, 4, 0),
15955            indent_guide(buffer_id, 2, 3, 1),
15956            indent_guide(buffer_id, 3, 3, 2),
15957        ],
15958        None,
15959        &mut cx,
15960    );
15961}
15962
15963#[gpui::test]
15964async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15965    let (buffer_id, mut cx) = setup_indent_guides_editor(
15966        &"
15967        block1
15968            block2
15969                block3
15970
15971        block1
15972        block1"
15973            .unindent(),
15974        cx,
15975    )
15976    .await;
15977
15978    assert_indent_guides(
15979        0..6,
15980        vec![
15981            indent_guide(buffer_id, 1, 2, 0),
15982            indent_guide(buffer_id, 2, 2, 1),
15983        ],
15984        None,
15985        &mut cx,
15986    );
15987}
15988
15989#[gpui::test]
15990async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15991    let (buffer_id, mut cx) = setup_indent_guides_editor(
15992        &"
15993        block1
15994
15995
15996
15997            block2
15998        "
15999        .unindent(),
16000        cx,
16001    )
16002    .await;
16003
16004    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16005}
16006
16007#[gpui::test]
16008async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16009    let (buffer_id, mut cx) = setup_indent_guides_editor(
16010        &"
16011        def a:
16012        \tb = 3
16013        \tif True:
16014        \t\tc = 4
16015        \t\td = 5
16016        \tprint(b)
16017        "
16018        .unindent(),
16019        cx,
16020    )
16021    .await;
16022
16023    assert_indent_guides(
16024        0..6,
16025        vec![
16026            indent_guide(buffer_id, 1, 6, 0),
16027            indent_guide(buffer_id, 3, 4, 1),
16028        ],
16029        None,
16030        &mut cx,
16031    );
16032}
16033
16034#[gpui::test]
16035async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16036    let (buffer_id, mut cx) = setup_indent_guides_editor(
16037        &"
16038    fn main() {
16039        let a = 1;
16040    }"
16041        .unindent(),
16042        cx,
16043    )
16044    .await;
16045
16046    cx.update_editor(|editor, window, cx| {
16047        editor.change_selections(None, window, cx, |s| {
16048            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16049        });
16050    });
16051
16052    assert_indent_guides(
16053        0..3,
16054        vec![indent_guide(buffer_id, 1, 1, 0)],
16055        Some(vec![0]),
16056        &mut cx,
16057    );
16058}
16059
16060#[gpui::test]
16061async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16062    let (buffer_id, mut cx) = setup_indent_guides_editor(
16063        &"
16064    fn main() {
16065        if 1 == 2 {
16066            let a = 1;
16067        }
16068    }"
16069        .unindent(),
16070        cx,
16071    )
16072    .await;
16073
16074    cx.update_editor(|editor, window, cx| {
16075        editor.change_selections(None, window, cx, |s| {
16076            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16077        });
16078    });
16079
16080    assert_indent_guides(
16081        0..4,
16082        vec![
16083            indent_guide(buffer_id, 1, 3, 0),
16084            indent_guide(buffer_id, 2, 2, 1),
16085        ],
16086        Some(vec![1]),
16087        &mut cx,
16088    );
16089
16090    cx.update_editor(|editor, window, cx| {
16091        editor.change_selections(None, window, cx, |s| {
16092            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16093        });
16094    });
16095
16096    assert_indent_guides(
16097        0..4,
16098        vec![
16099            indent_guide(buffer_id, 1, 3, 0),
16100            indent_guide(buffer_id, 2, 2, 1),
16101        ],
16102        Some(vec![1]),
16103        &mut cx,
16104    );
16105
16106    cx.update_editor(|editor, window, cx| {
16107        editor.change_selections(None, window, cx, |s| {
16108            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16109        });
16110    });
16111
16112    assert_indent_guides(
16113        0..4,
16114        vec![
16115            indent_guide(buffer_id, 1, 3, 0),
16116            indent_guide(buffer_id, 2, 2, 1),
16117        ],
16118        Some(vec![0]),
16119        &mut cx,
16120    );
16121}
16122
16123#[gpui::test]
16124async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16125    let (buffer_id, mut cx) = setup_indent_guides_editor(
16126        &"
16127    fn main() {
16128        let a = 1;
16129
16130        let b = 2;
16131    }"
16132        .unindent(),
16133        cx,
16134    )
16135    .await;
16136
16137    cx.update_editor(|editor, window, cx| {
16138        editor.change_selections(None, window, cx, |s| {
16139            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16140        });
16141    });
16142
16143    assert_indent_guides(
16144        0..5,
16145        vec![indent_guide(buffer_id, 1, 3, 0)],
16146        Some(vec![0]),
16147        &mut cx,
16148    );
16149}
16150
16151#[gpui::test]
16152async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16153    let (buffer_id, mut cx) = setup_indent_guides_editor(
16154        &"
16155    def m:
16156        a = 1
16157        pass"
16158            .unindent(),
16159        cx,
16160    )
16161    .await;
16162
16163    cx.update_editor(|editor, window, cx| {
16164        editor.change_selections(None, window, cx, |s| {
16165            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16166        });
16167    });
16168
16169    assert_indent_guides(
16170        0..3,
16171        vec![indent_guide(buffer_id, 1, 2, 0)],
16172        Some(vec![0]),
16173        &mut cx,
16174    );
16175}
16176
16177#[gpui::test]
16178async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16179    init_test(cx, |_| {});
16180    let mut cx = EditorTestContext::new(cx).await;
16181    let text = indoc! {
16182        "
16183        impl A {
16184            fn b() {
16185                0;
16186                3;
16187                5;
16188                6;
16189                7;
16190            }
16191        }
16192        "
16193    };
16194    let base_text = indoc! {
16195        "
16196        impl A {
16197            fn b() {
16198                0;
16199                1;
16200                2;
16201                3;
16202                4;
16203            }
16204            fn c() {
16205                5;
16206                6;
16207                7;
16208            }
16209        }
16210        "
16211    };
16212
16213    cx.update_editor(|editor, window, cx| {
16214        editor.set_text(text, window, cx);
16215
16216        editor.buffer().update(cx, |multibuffer, cx| {
16217            let buffer = multibuffer.as_singleton().unwrap();
16218            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16219
16220            multibuffer.set_all_diff_hunks_expanded(cx);
16221            multibuffer.add_diff(diff, cx);
16222
16223            buffer.read(cx).remote_id()
16224        })
16225    });
16226    cx.run_until_parked();
16227
16228    cx.assert_state_with_diff(
16229        indoc! { "
16230          impl A {
16231              fn b() {
16232                  0;
16233        -         1;
16234        -         2;
16235                  3;
16236        -         4;
16237        -     }
16238        -     fn c() {
16239                  5;
16240                  6;
16241                  7;
16242              }
16243          }
16244          ˇ"
16245        }
16246        .to_string(),
16247    );
16248
16249    let mut actual_guides = cx.update_editor(|editor, window, cx| {
16250        editor
16251            .snapshot(window, cx)
16252            .buffer_snapshot
16253            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16254            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16255            .collect::<Vec<_>>()
16256    });
16257    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16258    assert_eq!(
16259        actual_guides,
16260        vec![
16261            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16262            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16263            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16264        ]
16265    );
16266}
16267
16268#[gpui::test]
16269async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16270    init_test(cx, |_| {});
16271    let mut cx = EditorTestContext::new(cx).await;
16272
16273    let diff_base = r#"
16274        a
16275        b
16276        c
16277        "#
16278    .unindent();
16279
16280    cx.set_state(
16281        &r#"
16282        ˇA
16283        b
16284        C
16285        "#
16286        .unindent(),
16287    );
16288    cx.set_head_text(&diff_base);
16289    cx.update_editor(|editor, window, cx| {
16290        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16291    });
16292    executor.run_until_parked();
16293
16294    let both_hunks_expanded = r#"
16295        - a
16296        + ˇA
16297          b
16298        - c
16299        + C
16300        "#
16301    .unindent();
16302
16303    cx.assert_state_with_diff(both_hunks_expanded.clone());
16304
16305    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16306        let snapshot = editor.snapshot(window, cx);
16307        let hunks = editor
16308            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16309            .collect::<Vec<_>>();
16310        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16311        let buffer_id = hunks[0].buffer_id;
16312        hunks
16313            .into_iter()
16314            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16315            .collect::<Vec<_>>()
16316    });
16317    assert_eq!(hunk_ranges.len(), 2);
16318
16319    cx.update_editor(|editor, _, cx| {
16320        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16321    });
16322    executor.run_until_parked();
16323
16324    let second_hunk_expanded = r#"
16325          ˇA
16326          b
16327        - c
16328        + C
16329        "#
16330    .unindent();
16331
16332    cx.assert_state_with_diff(second_hunk_expanded);
16333
16334    cx.update_editor(|editor, _, cx| {
16335        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16336    });
16337    executor.run_until_parked();
16338
16339    cx.assert_state_with_diff(both_hunks_expanded.clone());
16340
16341    cx.update_editor(|editor, _, cx| {
16342        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16343    });
16344    executor.run_until_parked();
16345
16346    let first_hunk_expanded = r#"
16347        - a
16348        + ˇA
16349          b
16350          C
16351        "#
16352    .unindent();
16353
16354    cx.assert_state_with_diff(first_hunk_expanded);
16355
16356    cx.update_editor(|editor, _, cx| {
16357        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16358    });
16359    executor.run_until_parked();
16360
16361    cx.assert_state_with_diff(both_hunks_expanded);
16362
16363    cx.set_state(
16364        &r#"
16365        ˇA
16366        b
16367        "#
16368        .unindent(),
16369    );
16370    cx.run_until_parked();
16371
16372    // TODO this cursor position seems bad
16373    cx.assert_state_with_diff(
16374        r#"
16375        - ˇa
16376        + A
16377          b
16378        "#
16379        .unindent(),
16380    );
16381
16382    cx.update_editor(|editor, window, cx| {
16383        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16384    });
16385
16386    cx.assert_state_with_diff(
16387        r#"
16388            - ˇa
16389            + A
16390              b
16391            - c
16392            "#
16393        .unindent(),
16394    );
16395
16396    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16397        let snapshot = editor.snapshot(window, cx);
16398        let hunks = editor
16399            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16400            .collect::<Vec<_>>();
16401        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16402        let buffer_id = hunks[0].buffer_id;
16403        hunks
16404            .into_iter()
16405            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16406            .collect::<Vec<_>>()
16407    });
16408    assert_eq!(hunk_ranges.len(), 2);
16409
16410    cx.update_editor(|editor, _, cx| {
16411        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16412    });
16413    executor.run_until_parked();
16414
16415    cx.assert_state_with_diff(
16416        r#"
16417        - ˇa
16418        + A
16419          b
16420        "#
16421        .unindent(),
16422    );
16423}
16424
16425#[gpui::test]
16426async fn test_toggle_deletion_hunk_at_start_of_file(
16427    executor: BackgroundExecutor,
16428    cx: &mut TestAppContext,
16429) {
16430    init_test(cx, |_| {});
16431    let mut cx = EditorTestContext::new(cx).await;
16432
16433    let diff_base = r#"
16434        a
16435        b
16436        c
16437        "#
16438    .unindent();
16439
16440    cx.set_state(
16441        &r#"
16442        ˇb
16443        c
16444        "#
16445        .unindent(),
16446    );
16447    cx.set_head_text(&diff_base);
16448    cx.update_editor(|editor, window, cx| {
16449        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16450    });
16451    executor.run_until_parked();
16452
16453    let hunk_expanded = r#"
16454        - a
16455          ˇb
16456          c
16457        "#
16458    .unindent();
16459
16460    cx.assert_state_with_diff(hunk_expanded.clone());
16461
16462    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16463        let snapshot = editor.snapshot(window, cx);
16464        let hunks = editor
16465            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16466            .collect::<Vec<_>>();
16467        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16468        let buffer_id = hunks[0].buffer_id;
16469        hunks
16470            .into_iter()
16471            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16472            .collect::<Vec<_>>()
16473    });
16474    assert_eq!(hunk_ranges.len(), 1);
16475
16476    cx.update_editor(|editor, _, cx| {
16477        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16478    });
16479    executor.run_until_parked();
16480
16481    let hunk_collapsed = r#"
16482          ˇb
16483          c
16484        "#
16485    .unindent();
16486
16487    cx.assert_state_with_diff(hunk_collapsed);
16488
16489    cx.update_editor(|editor, _, cx| {
16490        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16491    });
16492    executor.run_until_parked();
16493
16494    cx.assert_state_with_diff(hunk_expanded.clone());
16495}
16496
16497#[gpui::test]
16498async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16499    init_test(cx, |_| {});
16500
16501    let fs = FakeFs::new(cx.executor());
16502    fs.insert_tree(
16503        path!("/test"),
16504        json!({
16505            ".git": {},
16506            "file-1": "ONE\n",
16507            "file-2": "TWO\n",
16508            "file-3": "THREE\n",
16509        }),
16510    )
16511    .await;
16512
16513    fs.set_head_for_repo(
16514        path!("/test/.git").as_ref(),
16515        &[
16516            ("file-1".into(), "one\n".into()),
16517            ("file-2".into(), "two\n".into()),
16518            ("file-3".into(), "three\n".into()),
16519        ],
16520    );
16521
16522    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16523    let mut buffers = vec![];
16524    for i in 1..=3 {
16525        let buffer = project
16526            .update(cx, |project, cx| {
16527                let path = format!(path!("/test/file-{}"), i);
16528                project.open_local_buffer(path, cx)
16529            })
16530            .await
16531            .unwrap();
16532        buffers.push(buffer);
16533    }
16534
16535    let multibuffer = cx.new(|cx| {
16536        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16537        multibuffer.set_all_diff_hunks_expanded(cx);
16538        for buffer in &buffers {
16539            let snapshot = buffer.read(cx).snapshot();
16540            multibuffer.set_excerpts_for_path(
16541                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16542                buffer.clone(),
16543                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16544                DEFAULT_MULTIBUFFER_CONTEXT,
16545                cx,
16546            );
16547        }
16548        multibuffer
16549    });
16550
16551    let editor = cx.add_window(|window, cx| {
16552        Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
16553    });
16554    cx.run_until_parked();
16555
16556    let snapshot = editor
16557        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16558        .unwrap();
16559    let hunks = snapshot
16560        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16561        .map(|hunk| match hunk {
16562            DisplayDiffHunk::Unfolded {
16563                display_row_range, ..
16564            } => display_row_range,
16565            DisplayDiffHunk::Folded { .. } => unreachable!(),
16566        })
16567        .collect::<Vec<_>>();
16568    assert_eq!(
16569        hunks,
16570        [
16571            DisplayRow(2)..DisplayRow(4),
16572            DisplayRow(7)..DisplayRow(9),
16573            DisplayRow(12)..DisplayRow(14),
16574        ]
16575    );
16576}
16577
16578#[gpui::test]
16579async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16580    init_test(cx, |_| {});
16581
16582    let mut cx = EditorTestContext::new(cx).await;
16583    cx.set_head_text(indoc! { "
16584        one
16585        two
16586        three
16587        four
16588        five
16589        "
16590    });
16591    cx.set_index_text(indoc! { "
16592        one
16593        two
16594        three
16595        four
16596        five
16597        "
16598    });
16599    cx.set_state(indoc! {"
16600        one
16601        TWO
16602        ˇTHREE
16603        FOUR
16604        five
16605    "});
16606    cx.run_until_parked();
16607    cx.update_editor(|editor, window, cx| {
16608        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16609    });
16610    cx.run_until_parked();
16611    cx.assert_index_text(Some(indoc! {"
16612        one
16613        TWO
16614        THREE
16615        FOUR
16616        five
16617    "}));
16618    cx.set_state(indoc! { "
16619        one
16620        TWO
16621        ˇTHREE-HUNDRED
16622        FOUR
16623        five
16624    "});
16625    cx.run_until_parked();
16626    cx.update_editor(|editor, window, cx| {
16627        let snapshot = editor.snapshot(window, cx);
16628        let hunks = editor
16629            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16630            .collect::<Vec<_>>();
16631        assert_eq!(hunks.len(), 1);
16632        assert_eq!(
16633            hunks[0].status(),
16634            DiffHunkStatus {
16635                kind: DiffHunkStatusKind::Modified,
16636                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16637            }
16638        );
16639
16640        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16641    });
16642    cx.run_until_parked();
16643    cx.assert_index_text(Some(indoc! {"
16644        one
16645        TWO
16646        THREE-HUNDRED
16647        FOUR
16648        five
16649    "}));
16650}
16651
16652#[gpui::test]
16653fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16654    init_test(cx, |_| {});
16655
16656    let editor = cx.add_window(|window, cx| {
16657        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16658        build_editor(buffer, window, cx)
16659    });
16660
16661    let render_args = Arc::new(Mutex::new(None));
16662    let snapshot = editor
16663        .update(cx, |editor, window, cx| {
16664            let snapshot = editor.buffer().read(cx).snapshot(cx);
16665            let range =
16666                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16667
16668            struct RenderArgs {
16669                row: MultiBufferRow,
16670                folded: bool,
16671                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16672            }
16673
16674            let crease = Crease::inline(
16675                range,
16676                FoldPlaceholder::test(),
16677                {
16678                    let toggle_callback = render_args.clone();
16679                    move |row, folded, callback, _window, _cx| {
16680                        *toggle_callback.lock() = Some(RenderArgs {
16681                            row,
16682                            folded,
16683                            callback,
16684                        });
16685                        div()
16686                    }
16687                },
16688                |_row, _folded, _window, _cx| div(),
16689            );
16690
16691            editor.insert_creases(Some(crease), cx);
16692            let snapshot = editor.snapshot(window, cx);
16693            let _div = snapshot.render_crease_toggle(
16694                MultiBufferRow(1),
16695                false,
16696                cx.entity().clone(),
16697                window,
16698                cx,
16699            );
16700            snapshot
16701        })
16702        .unwrap();
16703
16704    let render_args = render_args.lock().take().unwrap();
16705    assert_eq!(render_args.row, MultiBufferRow(1));
16706    assert!(!render_args.folded);
16707    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16708
16709    cx.update_window(*editor, |_, window, cx| {
16710        (render_args.callback)(true, window, cx)
16711    })
16712    .unwrap();
16713    let snapshot = editor
16714        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16715        .unwrap();
16716    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16717
16718    cx.update_window(*editor, |_, window, cx| {
16719        (render_args.callback)(false, window, cx)
16720    })
16721    .unwrap();
16722    let snapshot = editor
16723        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16724        .unwrap();
16725    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16726}
16727
16728#[gpui::test]
16729async fn test_input_text(cx: &mut TestAppContext) {
16730    init_test(cx, |_| {});
16731    let mut cx = EditorTestContext::new(cx).await;
16732
16733    cx.set_state(
16734        &r#"ˇone
16735        two
16736
16737        three
16738        fourˇ
16739        five
16740
16741        siˇx"#
16742            .unindent(),
16743    );
16744
16745    cx.dispatch_action(HandleInput(String::new()));
16746    cx.assert_editor_state(
16747        &r#"ˇone
16748        two
16749
16750        three
16751        fourˇ
16752        five
16753
16754        siˇx"#
16755            .unindent(),
16756    );
16757
16758    cx.dispatch_action(HandleInput("AAAA".to_string()));
16759    cx.assert_editor_state(
16760        &r#"AAAAˇone
16761        two
16762
16763        three
16764        fourAAAAˇ
16765        five
16766
16767        siAAAAˇx"#
16768            .unindent(),
16769    );
16770}
16771
16772#[gpui::test]
16773async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16774    init_test(cx, |_| {});
16775
16776    let mut cx = EditorTestContext::new(cx).await;
16777    cx.set_state(
16778        r#"let foo = 1;
16779let foo = 2;
16780let foo = 3;
16781let fooˇ = 4;
16782let foo = 5;
16783let foo = 6;
16784let foo = 7;
16785let foo = 8;
16786let foo = 9;
16787let foo = 10;
16788let foo = 11;
16789let foo = 12;
16790let foo = 13;
16791let foo = 14;
16792let foo = 15;"#,
16793    );
16794
16795    cx.update_editor(|e, window, cx| {
16796        assert_eq!(
16797            e.next_scroll_position,
16798            NextScrollCursorCenterTopBottom::Center,
16799            "Default next scroll direction is center",
16800        );
16801
16802        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16803        assert_eq!(
16804            e.next_scroll_position,
16805            NextScrollCursorCenterTopBottom::Top,
16806            "After center, next scroll direction should be top",
16807        );
16808
16809        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16810        assert_eq!(
16811            e.next_scroll_position,
16812            NextScrollCursorCenterTopBottom::Bottom,
16813            "After top, next scroll direction should be bottom",
16814        );
16815
16816        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16817        assert_eq!(
16818            e.next_scroll_position,
16819            NextScrollCursorCenterTopBottom::Center,
16820            "After bottom, scrolling should start over",
16821        );
16822
16823        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16824        assert_eq!(
16825            e.next_scroll_position,
16826            NextScrollCursorCenterTopBottom::Top,
16827            "Scrolling continues if retriggered fast enough"
16828        );
16829    });
16830
16831    cx.executor()
16832        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16833    cx.executor().run_until_parked();
16834    cx.update_editor(|e, _, _| {
16835        assert_eq!(
16836            e.next_scroll_position,
16837            NextScrollCursorCenterTopBottom::Center,
16838            "If scrolling is not triggered fast enough, it should reset"
16839        );
16840    });
16841}
16842
16843#[gpui::test]
16844async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16845    init_test(cx, |_| {});
16846    let mut cx = EditorLspTestContext::new_rust(
16847        lsp::ServerCapabilities {
16848            definition_provider: Some(lsp::OneOf::Left(true)),
16849            references_provider: Some(lsp::OneOf::Left(true)),
16850            ..lsp::ServerCapabilities::default()
16851        },
16852        cx,
16853    )
16854    .await;
16855
16856    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16857        let go_to_definition = cx
16858            .lsp
16859            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
16860                move |params, _| async move {
16861                    if empty_go_to_definition {
16862                        Ok(None)
16863                    } else {
16864                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16865                            uri: params.text_document_position_params.text_document.uri,
16866                            range: lsp::Range::new(
16867                                lsp::Position::new(4, 3),
16868                                lsp::Position::new(4, 6),
16869                            ),
16870                        })))
16871                    }
16872                },
16873            );
16874        let references = cx
16875            .lsp
16876            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
16877                Ok(Some(vec![lsp::Location {
16878                    uri: params.text_document_position.text_document.uri,
16879                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16880                }]))
16881            });
16882        (go_to_definition, references)
16883    };
16884
16885    cx.set_state(
16886        &r#"fn one() {
16887            let mut a = ˇtwo();
16888        }
16889
16890        fn two() {}"#
16891            .unindent(),
16892    );
16893    set_up_lsp_handlers(false, &mut cx);
16894    let navigated = cx
16895        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16896        .await
16897        .expect("Failed to navigate to definition");
16898    assert_eq!(
16899        navigated,
16900        Navigated::Yes,
16901        "Should have navigated to definition from the GetDefinition response"
16902    );
16903    cx.assert_editor_state(
16904        &r#"fn one() {
16905            let mut a = two();
16906        }
16907
16908        fn «twoˇ»() {}"#
16909            .unindent(),
16910    );
16911
16912    let editors = cx.update_workspace(|workspace, _, cx| {
16913        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16914    });
16915    cx.update_editor(|_, _, test_editor_cx| {
16916        assert_eq!(
16917            editors.len(),
16918            1,
16919            "Initially, only one, test, editor should be open in the workspace"
16920        );
16921        assert_eq!(
16922            test_editor_cx.entity(),
16923            editors.last().expect("Asserted len is 1").clone()
16924        );
16925    });
16926
16927    set_up_lsp_handlers(true, &mut cx);
16928    let navigated = cx
16929        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16930        .await
16931        .expect("Failed to navigate to lookup references");
16932    assert_eq!(
16933        navigated,
16934        Navigated::Yes,
16935        "Should have navigated to references as a fallback after empty GoToDefinition response"
16936    );
16937    // We should not change the selections in the existing file,
16938    // if opening another milti buffer with the references
16939    cx.assert_editor_state(
16940        &r#"fn one() {
16941            let mut a = two();
16942        }
16943
16944        fn «twoˇ»() {}"#
16945            .unindent(),
16946    );
16947    let editors = cx.update_workspace(|workspace, _, cx| {
16948        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16949    });
16950    cx.update_editor(|_, _, test_editor_cx| {
16951        assert_eq!(
16952            editors.len(),
16953            2,
16954            "After falling back to references search, we open a new editor with the results"
16955        );
16956        let references_fallback_text = editors
16957            .into_iter()
16958            .find(|new_editor| *new_editor != test_editor_cx.entity())
16959            .expect("Should have one non-test editor now")
16960            .read(test_editor_cx)
16961            .text(test_editor_cx);
16962        assert_eq!(
16963            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
16964            "Should use the range from the references response and not the GoToDefinition one"
16965        );
16966    });
16967}
16968
16969#[gpui::test]
16970async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
16971    init_test(cx, |_| {});
16972    cx.update(|cx| {
16973        let mut editor_settings = EditorSettings::get_global(cx).clone();
16974        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
16975        EditorSettings::override_global(editor_settings, cx);
16976    });
16977    let mut cx = EditorLspTestContext::new_rust(
16978        lsp::ServerCapabilities {
16979            definition_provider: Some(lsp::OneOf::Left(true)),
16980            references_provider: Some(lsp::OneOf::Left(true)),
16981            ..lsp::ServerCapabilities::default()
16982        },
16983        cx,
16984    )
16985    .await;
16986    let original_state = r#"fn one() {
16987        let mut a = ˇtwo();
16988    }
16989
16990    fn two() {}"#
16991        .unindent();
16992    cx.set_state(&original_state);
16993
16994    let mut go_to_definition = cx
16995        .lsp
16996        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
16997            move |_, _| async move { Ok(None) },
16998        );
16999    let _references = cx
17000        .lsp
17001        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17002            panic!("Should not call for references with no go to definition fallback")
17003        });
17004
17005    let navigated = cx
17006        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17007        .await
17008        .expect("Failed to navigate to lookup references");
17009    go_to_definition
17010        .next()
17011        .await
17012        .expect("Should have called the go_to_definition handler");
17013
17014    assert_eq!(
17015        navigated,
17016        Navigated::No,
17017        "Should have navigated to references as a fallback after empty GoToDefinition response"
17018    );
17019    cx.assert_editor_state(&original_state);
17020    let editors = cx.update_workspace(|workspace, _, cx| {
17021        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17022    });
17023    cx.update_editor(|_, _, _| {
17024        assert_eq!(
17025            editors.len(),
17026            1,
17027            "After unsuccessful fallback, no other editor should have been opened"
17028        );
17029    });
17030}
17031
17032#[gpui::test]
17033async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17034    init_test(cx, |_| {});
17035
17036    let language = Arc::new(Language::new(
17037        LanguageConfig::default(),
17038        Some(tree_sitter_rust::LANGUAGE.into()),
17039    ));
17040
17041    let text = r#"
17042        #[cfg(test)]
17043        mod tests() {
17044            #[test]
17045            fn runnable_1() {
17046                let a = 1;
17047            }
17048
17049            #[test]
17050            fn runnable_2() {
17051                let a = 1;
17052                let b = 2;
17053            }
17054        }
17055    "#
17056    .unindent();
17057
17058    let fs = FakeFs::new(cx.executor());
17059    fs.insert_file("/file.rs", Default::default()).await;
17060
17061    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17062    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17063    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17064    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17065    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17066
17067    let editor = cx.new_window_entity(|window, cx| {
17068        Editor::new(
17069            EditorMode::Full,
17070            multi_buffer,
17071            Some(project.clone()),
17072            window,
17073            cx,
17074        )
17075    });
17076
17077    editor.update_in(cx, |editor, window, cx| {
17078        let snapshot = editor.buffer().read(cx).snapshot(cx);
17079        editor.tasks.insert(
17080            (buffer.read(cx).remote_id(), 3),
17081            RunnableTasks {
17082                templates: vec![],
17083                offset: snapshot.anchor_before(43),
17084                column: 0,
17085                extra_variables: HashMap::default(),
17086                context_range: BufferOffset(43)..BufferOffset(85),
17087            },
17088        );
17089        editor.tasks.insert(
17090            (buffer.read(cx).remote_id(), 8),
17091            RunnableTasks {
17092                templates: vec![],
17093                offset: snapshot.anchor_before(86),
17094                column: 0,
17095                extra_variables: HashMap::default(),
17096                context_range: BufferOffset(86)..BufferOffset(191),
17097            },
17098        );
17099
17100        // Test finding task when cursor is inside function body
17101        editor.change_selections(None, window, cx, |s| {
17102            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17103        });
17104        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17105        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17106
17107        // Test finding task when cursor is on function name
17108        editor.change_selections(None, window, cx, |s| {
17109            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17110        });
17111        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17112        assert_eq!(row, 8, "Should find task when cursor is on function name");
17113    });
17114}
17115
17116#[gpui::test]
17117async fn test_folding_buffers(cx: &mut TestAppContext) {
17118    init_test(cx, |_| {});
17119
17120    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17121    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17122    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17123
17124    let fs = FakeFs::new(cx.executor());
17125    fs.insert_tree(
17126        path!("/a"),
17127        json!({
17128            "first.rs": sample_text_1,
17129            "second.rs": sample_text_2,
17130            "third.rs": sample_text_3,
17131        }),
17132    )
17133    .await;
17134    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17135    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17136    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17137    let worktree = project.update(cx, |project, cx| {
17138        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17139        assert_eq!(worktrees.len(), 1);
17140        worktrees.pop().unwrap()
17141    });
17142    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17143
17144    let buffer_1 = project
17145        .update(cx, |project, cx| {
17146            project.open_buffer((worktree_id, "first.rs"), cx)
17147        })
17148        .await
17149        .unwrap();
17150    let buffer_2 = project
17151        .update(cx, |project, cx| {
17152            project.open_buffer((worktree_id, "second.rs"), cx)
17153        })
17154        .await
17155        .unwrap();
17156    let buffer_3 = project
17157        .update(cx, |project, cx| {
17158            project.open_buffer((worktree_id, "third.rs"), cx)
17159        })
17160        .await
17161        .unwrap();
17162
17163    let multi_buffer = cx.new(|cx| {
17164        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17165        multi_buffer.push_excerpts(
17166            buffer_1.clone(),
17167            [
17168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17171            ],
17172            cx,
17173        );
17174        multi_buffer.push_excerpts(
17175            buffer_2.clone(),
17176            [
17177                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17178                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17179                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17180            ],
17181            cx,
17182        );
17183        multi_buffer.push_excerpts(
17184            buffer_3.clone(),
17185            [
17186                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17187                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17188                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17189            ],
17190            cx,
17191        );
17192        multi_buffer
17193    });
17194    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17195        Editor::new(
17196            EditorMode::Full,
17197            multi_buffer.clone(),
17198            Some(project.clone()),
17199            window,
17200            cx,
17201        )
17202    });
17203
17204    assert_eq!(
17205        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17206        "\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",
17207    );
17208
17209    multi_buffer_editor.update(cx, |editor, cx| {
17210        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17211    });
17212    assert_eq!(
17213        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17214        "\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",
17215        "After folding the first buffer, its text should not be displayed"
17216    );
17217
17218    multi_buffer_editor.update(cx, |editor, cx| {
17219        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17220    });
17221    assert_eq!(
17222        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17223        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17224        "After folding the second buffer, its text should not be displayed"
17225    );
17226
17227    multi_buffer_editor.update(cx, |editor, cx| {
17228        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17229    });
17230    assert_eq!(
17231        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17232        "\n\n\n\n\n",
17233        "After folding the third buffer, its text should not be displayed"
17234    );
17235
17236    // Emulate selection inside the fold logic, that should work
17237    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17238        editor
17239            .snapshot(window, cx)
17240            .next_line_boundary(Point::new(0, 4));
17241    });
17242
17243    multi_buffer_editor.update(cx, |editor, cx| {
17244        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17245    });
17246    assert_eq!(
17247        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17248        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17249        "After unfolding the second buffer, its text should be displayed"
17250    );
17251
17252    // Typing inside of buffer 1 causes that buffer to be unfolded.
17253    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17254        assert_eq!(
17255            multi_buffer
17256                .read(cx)
17257                .snapshot(cx)
17258                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17259                .collect::<String>(),
17260            "bbbb"
17261        );
17262        editor.change_selections(None, window, cx, |selections| {
17263            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17264        });
17265        editor.handle_input("B", window, cx);
17266    });
17267
17268    assert_eq!(
17269        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17270        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17271        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17272    );
17273
17274    multi_buffer_editor.update(cx, |editor, cx| {
17275        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17276    });
17277    assert_eq!(
17278        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17279        "\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",
17280        "After unfolding the all buffers, all original text should be displayed"
17281    );
17282}
17283
17284#[gpui::test]
17285async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17286    init_test(cx, |_| {});
17287
17288    let sample_text_1 = "1111\n2222\n3333".to_string();
17289    let sample_text_2 = "4444\n5555\n6666".to_string();
17290    let sample_text_3 = "7777\n8888\n9999".to_string();
17291
17292    let fs = FakeFs::new(cx.executor());
17293    fs.insert_tree(
17294        path!("/a"),
17295        json!({
17296            "first.rs": sample_text_1,
17297            "second.rs": sample_text_2,
17298            "third.rs": sample_text_3,
17299        }),
17300    )
17301    .await;
17302    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17303    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17304    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17305    let worktree = project.update(cx, |project, cx| {
17306        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17307        assert_eq!(worktrees.len(), 1);
17308        worktrees.pop().unwrap()
17309    });
17310    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17311
17312    let buffer_1 = project
17313        .update(cx, |project, cx| {
17314            project.open_buffer((worktree_id, "first.rs"), cx)
17315        })
17316        .await
17317        .unwrap();
17318    let buffer_2 = project
17319        .update(cx, |project, cx| {
17320            project.open_buffer((worktree_id, "second.rs"), cx)
17321        })
17322        .await
17323        .unwrap();
17324    let buffer_3 = project
17325        .update(cx, |project, cx| {
17326            project.open_buffer((worktree_id, "third.rs"), cx)
17327        })
17328        .await
17329        .unwrap();
17330
17331    let multi_buffer = cx.new(|cx| {
17332        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17333        multi_buffer.push_excerpts(
17334            buffer_1.clone(),
17335            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17336            cx,
17337        );
17338        multi_buffer.push_excerpts(
17339            buffer_2.clone(),
17340            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17341            cx,
17342        );
17343        multi_buffer.push_excerpts(
17344            buffer_3.clone(),
17345            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17346            cx,
17347        );
17348        multi_buffer
17349    });
17350
17351    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17352        Editor::new(
17353            EditorMode::Full,
17354            multi_buffer,
17355            Some(project.clone()),
17356            window,
17357            cx,
17358        )
17359    });
17360
17361    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17362    assert_eq!(
17363        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17364        full_text,
17365    );
17366
17367    multi_buffer_editor.update(cx, |editor, cx| {
17368        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17369    });
17370    assert_eq!(
17371        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17372        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17373        "After folding the first buffer, its text should not be displayed"
17374    );
17375
17376    multi_buffer_editor.update(cx, |editor, cx| {
17377        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17378    });
17379
17380    assert_eq!(
17381        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17382        "\n\n\n\n\n\n7777\n8888\n9999",
17383        "After folding the second buffer, its text should not be displayed"
17384    );
17385
17386    multi_buffer_editor.update(cx, |editor, cx| {
17387        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17388    });
17389    assert_eq!(
17390        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17391        "\n\n\n\n\n",
17392        "After folding the third buffer, its text should not be displayed"
17393    );
17394
17395    multi_buffer_editor.update(cx, |editor, cx| {
17396        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17397    });
17398    assert_eq!(
17399        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17400        "\n\n\n\n4444\n5555\n6666\n\n",
17401        "After unfolding the second buffer, its text should be displayed"
17402    );
17403
17404    multi_buffer_editor.update(cx, |editor, cx| {
17405        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17406    });
17407    assert_eq!(
17408        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17409        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17410        "After unfolding the first buffer, its text should be displayed"
17411    );
17412
17413    multi_buffer_editor.update(cx, |editor, cx| {
17414        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17415    });
17416    assert_eq!(
17417        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17418        full_text,
17419        "After unfolding all buffers, all original text should be displayed"
17420    );
17421}
17422
17423#[gpui::test]
17424async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17425    init_test(cx, |_| {});
17426
17427    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17428
17429    let fs = FakeFs::new(cx.executor());
17430    fs.insert_tree(
17431        path!("/a"),
17432        json!({
17433            "main.rs": sample_text,
17434        }),
17435    )
17436    .await;
17437    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17438    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17439    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17440    let worktree = project.update(cx, |project, cx| {
17441        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17442        assert_eq!(worktrees.len(), 1);
17443        worktrees.pop().unwrap()
17444    });
17445    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17446
17447    let buffer_1 = project
17448        .update(cx, |project, cx| {
17449            project.open_buffer((worktree_id, "main.rs"), cx)
17450        })
17451        .await
17452        .unwrap();
17453
17454    let multi_buffer = cx.new(|cx| {
17455        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17456        multi_buffer.push_excerpts(
17457            buffer_1.clone(),
17458            [ExcerptRange::new(
17459                Point::new(0, 0)
17460                    ..Point::new(
17461                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17462                        0,
17463                    ),
17464            )],
17465            cx,
17466        );
17467        multi_buffer
17468    });
17469    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17470        Editor::new(
17471            EditorMode::Full,
17472            multi_buffer,
17473            Some(project.clone()),
17474            window,
17475            cx,
17476        )
17477    });
17478
17479    let selection_range = Point::new(1, 0)..Point::new(2, 0);
17480    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17481        enum TestHighlight {}
17482        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17483        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17484        editor.highlight_text::<TestHighlight>(
17485            vec![highlight_range.clone()],
17486            HighlightStyle::color(Hsla::green()),
17487            cx,
17488        );
17489        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17490    });
17491
17492    let full_text = format!("\n\n{sample_text}");
17493    assert_eq!(
17494        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17495        full_text,
17496    );
17497}
17498
17499#[gpui::test]
17500async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17501    init_test(cx, |_| {});
17502    cx.update(|cx| {
17503        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17504            "keymaps/default-linux.json",
17505            cx,
17506        )
17507        .unwrap();
17508        cx.bind_keys(default_key_bindings);
17509    });
17510
17511    let (editor, cx) = cx.add_window_view(|window, cx| {
17512        let multi_buffer = MultiBuffer::build_multi(
17513            [
17514                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17515                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17516                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17517                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17518            ],
17519            cx,
17520        );
17521        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
17522
17523        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17524        // fold all but the second buffer, so that we test navigating between two
17525        // adjacent folded buffers, as well as folded buffers at the start and
17526        // end the multibuffer
17527        editor.fold_buffer(buffer_ids[0], cx);
17528        editor.fold_buffer(buffer_ids[2], cx);
17529        editor.fold_buffer(buffer_ids[3], cx);
17530
17531        editor
17532    });
17533    cx.simulate_resize(size(px(1000.), px(1000.)));
17534
17535    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17536    cx.assert_excerpts_with_selections(indoc! {"
17537        [EXCERPT]
17538        ˇ[FOLDED]
17539        [EXCERPT]
17540        a1
17541        b1
17542        [EXCERPT]
17543        [FOLDED]
17544        [EXCERPT]
17545        [FOLDED]
17546        "
17547    });
17548    cx.simulate_keystroke("down");
17549    cx.assert_excerpts_with_selections(indoc! {"
17550        [EXCERPT]
17551        [FOLDED]
17552        [EXCERPT]
17553        ˇa1
17554        b1
17555        [EXCERPT]
17556        [FOLDED]
17557        [EXCERPT]
17558        [FOLDED]
17559        "
17560    });
17561    cx.simulate_keystroke("down");
17562    cx.assert_excerpts_with_selections(indoc! {"
17563        [EXCERPT]
17564        [FOLDED]
17565        [EXCERPT]
17566        a1
17567        ˇb1
17568        [EXCERPT]
17569        [FOLDED]
17570        [EXCERPT]
17571        [FOLDED]
17572        "
17573    });
17574    cx.simulate_keystroke("down");
17575    cx.assert_excerpts_with_selections(indoc! {"
17576        [EXCERPT]
17577        [FOLDED]
17578        [EXCERPT]
17579        a1
17580        b1
17581        ˇ[EXCERPT]
17582        [FOLDED]
17583        [EXCERPT]
17584        [FOLDED]
17585        "
17586    });
17587    cx.simulate_keystroke("down");
17588    cx.assert_excerpts_with_selections(indoc! {"
17589        [EXCERPT]
17590        [FOLDED]
17591        [EXCERPT]
17592        a1
17593        b1
17594        [EXCERPT]
17595        ˇ[FOLDED]
17596        [EXCERPT]
17597        [FOLDED]
17598        "
17599    });
17600    for _ in 0..5 {
17601        cx.simulate_keystroke("down");
17602        cx.assert_excerpts_with_selections(indoc! {"
17603            [EXCERPT]
17604            [FOLDED]
17605            [EXCERPT]
17606            a1
17607            b1
17608            [EXCERPT]
17609            [FOLDED]
17610            [EXCERPT]
17611            ˇ[FOLDED]
17612            "
17613        });
17614    }
17615
17616    cx.simulate_keystroke("up");
17617    cx.assert_excerpts_with_selections(indoc! {"
17618        [EXCERPT]
17619        [FOLDED]
17620        [EXCERPT]
17621        a1
17622        b1
17623        [EXCERPT]
17624        ˇ[FOLDED]
17625        [EXCERPT]
17626        [FOLDED]
17627        "
17628    });
17629    cx.simulate_keystroke("up");
17630    cx.assert_excerpts_with_selections(indoc! {"
17631        [EXCERPT]
17632        [FOLDED]
17633        [EXCERPT]
17634        a1
17635        b1
17636        ˇ[EXCERPT]
17637        [FOLDED]
17638        [EXCERPT]
17639        [FOLDED]
17640        "
17641    });
17642    cx.simulate_keystroke("up");
17643    cx.assert_excerpts_with_selections(indoc! {"
17644        [EXCERPT]
17645        [FOLDED]
17646        [EXCERPT]
17647        a1
17648        ˇb1
17649        [EXCERPT]
17650        [FOLDED]
17651        [EXCERPT]
17652        [FOLDED]
17653        "
17654    });
17655    cx.simulate_keystroke("up");
17656    cx.assert_excerpts_with_selections(indoc! {"
17657        [EXCERPT]
17658        [FOLDED]
17659        [EXCERPT]
17660        ˇa1
17661        b1
17662        [EXCERPT]
17663        [FOLDED]
17664        [EXCERPT]
17665        [FOLDED]
17666        "
17667    });
17668    for _ in 0..5 {
17669        cx.simulate_keystroke("up");
17670        cx.assert_excerpts_with_selections(indoc! {"
17671            [EXCERPT]
17672            ˇ[FOLDED]
17673            [EXCERPT]
17674            a1
17675            b1
17676            [EXCERPT]
17677            [FOLDED]
17678            [EXCERPT]
17679            [FOLDED]
17680            "
17681        });
17682    }
17683}
17684
17685#[gpui::test]
17686async fn test_inline_completion_text(cx: &mut TestAppContext) {
17687    init_test(cx, |_| {});
17688
17689    // Simple insertion
17690    assert_highlighted_edits(
17691        "Hello, world!",
17692        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
17693        true,
17694        cx,
17695        |highlighted_edits, cx| {
17696            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
17697            assert_eq!(highlighted_edits.highlights.len(), 1);
17698            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
17699            assert_eq!(
17700                highlighted_edits.highlights[0].1.background_color,
17701                Some(cx.theme().status().created_background)
17702            );
17703        },
17704    )
17705    .await;
17706
17707    // Replacement
17708    assert_highlighted_edits(
17709        "This is a test.",
17710        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17711        false,
17712        cx,
17713        |highlighted_edits, cx| {
17714            assert_eq!(highlighted_edits.text, "That is a test.");
17715            assert_eq!(highlighted_edits.highlights.len(), 1);
17716            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17717            assert_eq!(
17718                highlighted_edits.highlights[0].1.background_color,
17719                Some(cx.theme().status().created_background)
17720            );
17721        },
17722    )
17723    .await;
17724
17725    // Multiple edits
17726    assert_highlighted_edits(
17727        "Hello, world!",
17728        vec![
17729            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17730            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17731        ],
17732        false,
17733        cx,
17734        |highlighted_edits, cx| {
17735            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17736            assert_eq!(highlighted_edits.highlights.len(), 2);
17737            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17738            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17739            assert_eq!(
17740                highlighted_edits.highlights[0].1.background_color,
17741                Some(cx.theme().status().created_background)
17742            );
17743            assert_eq!(
17744                highlighted_edits.highlights[1].1.background_color,
17745                Some(cx.theme().status().created_background)
17746            );
17747        },
17748    )
17749    .await;
17750
17751    // Multiple lines with edits
17752    assert_highlighted_edits(
17753        "First line\nSecond line\nThird line\nFourth line",
17754        vec![
17755            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17756            (
17757                Point::new(2, 0)..Point::new(2, 10),
17758                "New third line".to_string(),
17759            ),
17760            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17761        ],
17762        false,
17763        cx,
17764        |highlighted_edits, cx| {
17765            assert_eq!(
17766                highlighted_edits.text,
17767                "Second modified\nNew third line\nFourth updated line"
17768            );
17769            assert_eq!(highlighted_edits.highlights.len(), 3);
17770            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17771            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17772            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17773            for highlight in &highlighted_edits.highlights {
17774                assert_eq!(
17775                    highlight.1.background_color,
17776                    Some(cx.theme().status().created_background)
17777                );
17778            }
17779        },
17780    )
17781    .await;
17782}
17783
17784#[gpui::test]
17785async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17786    init_test(cx, |_| {});
17787
17788    // Deletion
17789    assert_highlighted_edits(
17790        "Hello, world!",
17791        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17792        true,
17793        cx,
17794        |highlighted_edits, cx| {
17795            assert_eq!(highlighted_edits.text, "Hello, world!");
17796            assert_eq!(highlighted_edits.highlights.len(), 1);
17797            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17798            assert_eq!(
17799                highlighted_edits.highlights[0].1.background_color,
17800                Some(cx.theme().status().deleted_background)
17801            );
17802        },
17803    )
17804    .await;
17805
17806    // Insertion
17807    assert_highlighted_edits(
17808        "Hello, world!",
17809        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17810        true,
17811        cx,
17812        |highlighted_edits, cx| {
17813            assert_eq!(highlighted_edits.highlights.len(), 1);
17814            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17815            assert_eq!(
17816                highlighted_edits.highlights[0].1.background_color,
17817                Some(cx.theme().status().created_background)
17818            );
17819        },
17820    )
17821    .await;
17822}
17823
17824async fn assert_highlighted_edits(
17825    text: &str,
17826    edits: Vec<(Range<Point>, String)>,
17827    include_deletions: bool,
17828    cx: &mut TestAppContext,
17829    assertion_fn: impl Fn(HighlightedText, &App),
17830) {
17831    let window = cx.add_window(|window, cx| {
17832        let buffer = MultiBuffer::build_simple(text, cx);
17833        Editor::new(EditorMode::Full, buffer, None, window, cx)
17834    });
17835    let cx = &mut VisualTestContext::from_window(*window, cx);
17836
17837    let (buffer, snapshot) = window
17838        .update(cx, |editor, _window, cx| {
17839            (
17840                editor.buffer().clone(),
17841                editor.buffer().read(cx).snapshot(cx),
17842            )
17843        })
17844        .unwrap();
17845
17846    let edits = edits
17847        .into_iter()
17848        .map(|(range, edit)| {
17849            (
17850                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17851                edit,
17852            )
17853        })
17854        .collect::<Vec<_>>();
17855
17856    let text_anchor_edits = edits
17857        .clone()
17858        .into_iter()
17859        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17860        .collect::<Vec<_>>();
17861
17862    let edit_preview = window
17863        .update(cx, |_, _window, cx| {
17864            buffer
17865                .read(cx)
17866                .as_singleton()
17867                .unwrap()
17868                .read(cx)
17869                .preview_edits(text_anchor_edits.into(), cx)
17870        })
17871        .unwrap()
17872        .await;
17873
17874    cx.update(|_window, cx| {
17875        let highlighted_edits = inline_completion_edit_text(
17876            &snapshot.as_singleton().unwrap().2,
17877            &edits,
17878            &edit_preview,
17879            include_deletions,
17880            cx,
17881        );
17882        assertion_fn(highlighted_edits, cx)
17883    });
17884}
17885
17886#[track_caller]
17887fn assert_breakpoint(
17888    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
17889    path: &Arc<Path>,
17890    expected: Vec<(u32, Breakpoint)>,
17891) {
17892    if expected.len() == 0usize {
17893        assert!(!breakpoints.contains_key(path), "{}", path.display());
17894    } else {
17895        let mut breakpoint = breakpoints
17896            .get(path)
17897            .unwrap()
17898            .into_iter()
17899            .map(|breakpoint| {
17900                (
17901                    breakpoint.row,
17902                    Breakpoint {
17903                        message: breakpoint.message.clone(),
17904                        state: breakpoint.state,
17905                        condition: breakpoint.condition.clone(),
17906                        hit_condition: breakpoint.hit_condition.clone(),
17907                    },
17908                )
17909            })
17910            .collect::<Vec<_>>();
17911
17912        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17913
17914        assert_eq!(expected, breakpoint);
17915    }
17916}
17917
17918fn add_log_breakpoint_at_cursor(
17919    editor: &mut Editor,
17920    log_message: &str,
17921    window: &mut Window,
17922    cx: &mut Context<Editor>,
17923) {
17924    let (anchor, bp) = editor
17925        .breakpoint_at_cursor_head(window, cx)
17926        .unwrap_or_else(|| {
17927            let cursor_position: Point = editor.selections.newest(cx).head();
17928
17929            let breakpoint_position = editor
17930                .snapshot(window, cx)
17931                .display_snapshot
17932                .buffer_snapshot
17933                .anchor_before(Point::new(cursor_position.row, 0));
17934
17935            (breakpoint_position, Breakpoint::new_log(&log_message))
17936        });
17937
17938    editor.edit_breakpoint_at_anchor(
17939        anchor,
17940        bp,
17941        BreakpointEditAction::EditLogMessage(log_message.into()),
17942        cx,
17943    );
17944}
17945
17946#[gpui::test]
17947async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17948    init_test(cx, |_| {});
17949
17950    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17951    let fs = FakeFs::new(cx.executor());
17952    fs.insert_tree(
17953        path!("/a"),
17954        json!({
17955            "main.rs": sample_text,
17956        }),
17957    )
17958    .await;
17959    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17960    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17961    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17962
17963    let fs = FakeFs::new(cx.executor());
17964    fs.insert_tree(
17965        path!("/a"),
17966        json!({
17967            "main.rs": sample_text,
17968        }),
17969    )
17970    .await;
17971    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17972    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17973    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17974    let worktree_id = workspace
17975        .update(cx, |workspace, _window, cx| {
17976            workspace.project().update(cx, |project, cx| {
17977                project.worktrees(cx).next().unwrap().read(cx).id()
17978            })
17979        })
17980        .unwrap();
17981
17982    let buffer = project
17983        .update(cx, |project, cx| {
17984            project.open_buffer((worktree_id, "main.rs"), cx)
17985        })
17986        .await
17987        .unwrap();
17988
17989    let (editor, cx) = cx.add_window_view(|window, cx| {
17990        Editor::new(
17991            EditorMode::Full,
17992            MultiBuffer::build_from_buffer(buffer, cx),
17993            Some(project.clone()),
17994            window,
17995            cx,
17996        )
17997    });
17998
17999    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18000    let abs_path = project.read_with(cx, |project, cx| {
18001        project
18002            .absolute_path(&project_path, cx)
18003            .map(|path_buf| Arc::from(path_buf.to_owned()))
18004            .unwrap()
18005    });
18006
18007    // assert we can add breakpoint on the first line
18008    editor.update_in(cx, |editor, window, cx| {
18009        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18010        editor.move_to_end(&MoveToEnd, window, cx);
18011        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18012    });
18013
18014    let breakpoints = editor.update(cx, |editor, cx| {
18015        editor
18016            .breakpoint_store()
18017            .as_ref()
18018            .unwrap()
18019            .read(cx)
18020            .all_breakpoints(cx)
18021            .clone()
18022    });
18023
18024    assert_eq!(1, breakpoints.len());
18025    assert_breakpoint(
18026        &breakpoints,
18027        &abs_path,
18028        vec![
18029            (0, Breakpoint::new_standard()),
18030            (3, Breakpoint::new_standard()),
18031        ],
18032    );
18033
18034    editor.update_in(cx, |editor, window, cx| {
18035        editor.move_to_beginning(&MoveToBeginning, window, cx);
18036        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18037    });
18038
18039    let breakpoints = editor.update(cx, |editor, cx| {
18040        editor
18041            .breakpoint_store()
18042            .as_ref()
18043            .unwrap()
18044            .read(cx)
18045            .all_breakpoints(cx)
18046            .clone()
18047    });
18048
18049    assert_eq!(1, breakpoints.len());
18050    assert_breakpoint(
18051        &breakpoints,
18052        &abs_path,
18053        vec![(3, Breakpoint::new_standard())],
18054    );
18055
18056    editor.update_in(cx, |editor, window, cx| {
18057        editor.move_to_end(&MoveToEnd, window, cx);
18058        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18059    });
18060
18061    let breakpoints = editor.update(cx, |editor, cx| {
18062        editor
18063            .breakpoint_store()
18064            .as_ref()
18065            .unwrap()
18066            .read(cx)
18067            .all_breakpoints(cx)
18068            .clone()
18069    });
18070
18071    assert_eq!(0, breakpoints.len());
18072    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18073}
18074
18075#[gpui::test]
18076async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18077    init_test(cx, |_| {});
18078
18079    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18080
18081    let fs = FakeFs::new(cx.executor());
18082    fs.insert_tree(
18083        path!("/a"),
18084        json!({
18085            "main.rs": sample_text,
18086        }),
18087    )
18088    .await;
18089    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18090    let (workspace, cx) =
18091        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18092
18093    let worktree_id = workspace.update(cx, |workspace, cx| {
18094        workspace.project().update(cx, |project, cx| {
18095            project.worktrees(cx).next().unwrap().read(cx).id()
18096        })
18097    });
18098
18099    let buffer = project
18100        .update(cx, |project, cx| {
18101            project.open_buffer((worktree_id, "main.rs"), cx)
18102        })
18103        .await
18104        .unwrap();
18105
18106    let (editor, cx) = cx.add_window_view(|window, cx| {
18107        Editor::new(
18108            EditorMode::Full,
18109            MultiBuffer::build_from_buffer(buffer, cx),
18110            Some(project.clone()),
18111            window,
18112            cx,
18113        )
18114    });
18115
18116    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18117    let abs_path = project.read_with(cx, |project, cx| {
18118        project
18119            .absolute_path(&project_path, cx)
18120            .map(|path_buf| Arc::from(path_buf.to_owned()))
18121            .unwrap()
18122    });
18123
18124    editor.update_in(cx, |editor, window, cx| {
18125        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18126    });
18127
18128    let breakpoints = editor.update(cx, |editor, cx| {
18129        editor
18130            .breakpoint_store()
18131            .as_ref()
18132            .unwrap()
18133            .read(cx)
18134            .all_breakpoints(cx)
18135            .clone()
18136    });
18137
18138    assert_breakpoint(
18139        &breakpoints,
18140        &abs_path,
18141        vec![(0, Breakpoint::new_log("hello world"))],
18142    );
18143
18144    // Removing a log message from a log breakpoint should remove it
18145    editor.update_in(cx, |editor, window, cx| {
18146        add_log_breakpoint_at_cursor(editor, "", window, cx);
18147    });
18148
18149    let breakpoints = editor.update(cx, |editor, cx| {
18150        editor
18151            .breakpoint_store()
18152            .as_ref()
18153            .unwrap()
18154            .read(cx)
18155            .all_breakpoints(cx)
18156            .clone()
18157    });
18158
18159    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18160
18161    editor.update_in(cx, |editor, window, cx| {
18162        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18163        editor.move_to_end(&MoveToEnd, window, cx);
18164        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18165        // Not adding a log message to a standard breakpoint shouldn't remove it
18166        add_log_breakpoint_at_cursor(editor, "", window, cx);
18167    });
18168
18169    let breakpoints = editor.update(cx, |editor, cx| {
18170        editor
18171            .breakpoint_store()
18172            .as_ref()
18173            .unwrap()
18174            .read(cx)
18175            .all_breakpoints(cx)
18176            .clone()
18177    });
18178
18179    assert_breakpoint(
18180        &breakpoints,
18181        &abs_path,
18182        vec![
18183            (0, Breakpoint::new_standard()),
18184            (3, Breakpoint::new_standard()),
18185        ],
18186    );
18187
18188    editor.update_in(cx, |editor, window, cx| {
18189        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18190    });
18191
18192    let breakpoints = editor.update(cx, |editor, cx| {
18193        editor
18194            .breakpoint_store()
18195            .as_ref()
18196            .unwrap()
18197            .read(cx)
18198            .all_breakpoints(cx)
18199            .clone()
18200    });
18201
18202    assert_breakpoint(
18203        &breakpoints,
18204        &abs_path,
18205        vec![
18206            (0, Breakpoint::new_standard()),
18207            (3, Breakpoint::new_log("hello world")),
18208        ],
18209    );
18210
18211    editor.update_in(cx, |editor, window, cx| {
18212        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18213    });
18214
18215    let breakpoints = editor.update(cx, |editor, cx| {
18216        editor
18217            .breakpoint_store()
18218            .as_ref()
18219            .unwrap()
18220            .read(cx)
18221            .all_breakpoints(cx)
18222            .clone()
18223    });
18224
18225    assert_breakpoint(
18226        &breakpoints,
18227        &abs_path,
18228        vec![
18229            (0, Breakpoint::new_standard()),
18230            (3, Breakpoint::new_log("hello Earth!!")),
18231        ],
18232    );
18233}
18234
18235/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18236/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18237/// or when breakpoints were placed out of order. This tests for a regression too
18238#[gpui::test]
18239async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18240    init_test(cx, |_| {});
18241
18242    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18243    let fs = FakeFs::new(cx.executor());
18244    fs.insert_tree(
18245        path!("/a"),
18246        json!({
18247            "main.rs": sample_text,
18248        }),
18249    )
18250    .await;
18251    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18252    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18253    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18254
18255    let fs = FakeFs::new(cx.executor());
18256    fs.insert_tree(
18257        path!("/a"),
18258        json!({
18259            "main.rs": sample_text,
18260        }),
18261    )
18262    .await;
18263    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18264    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18265    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18266    let worktree_id = workspace
18267        .update(cx, |workspace, _window, cx| {
18268            workspace.project().update(cx, |project, cx| {
18269                project.worktrees(cx).next().unwrap().read(cx).id()
18270            })
18271        })
18272        .unwrap();
18273
18274    let buffer = project
18275        .update(cx, |project, cx| {
18276            project.open_buffer((worktree_id, "main.rs"), cx)
18277        })
18278        .await
18279        .unwrap();
18280
18281    let (editor, cx) = cx.add_window_view(|window, cx| {
18282        Editor::new(
18283            EditorMode::Full,
18284            MultiBuffer::build_from_buffer(buffer, cx),
18285            Some(project.clone()),
18286            window,
18287            cx,
18288        )
18289    });
18290
18291    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18292    let abs_path = project.read_with(cx, |project, cx| {
18293        project
18294            .absolute_path(&project_path, cx)
18295            .map(|path_buf| Arc::from(path_buf.to_owned()))
18296            .unwrap()
18297    });
18298
18299    // assert we can add breakpoint on the first line
18300    editor.update_in(cx, |editor, window, cx| {
18301        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18302        editor.move_to_end(&MoveToEnd, window, cx);
18303        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18304        editor.move_up(&MoveUp, window, cx);
18305        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18306    });
18307
18308    let breakpoints = editor.update(cx, |editor, cx| {
18309        editor
18310            .breakpoint_store()
18311            .as_ref()
18312            .unwrap()
18313            .read(cx)
18314            .all_breakpoints(cx)
18315            .clone()
18316    });
18317
18318    assert_eq!(1, breakpoints.len());
18319    assert_breakpoint(
18320        &breakpoints,
18321        &abs_path,
18322        vec![
18323            (0, Breakpoint::new_standard()),
18324            (2, Breakpoint::new_standard()),
18325            (3, Breakpoint::new_standard()),
18326        ],
18327    );
18328
18329    editor.update_in(cx, |editor, window, cx| {
18330        editor.move_to_beginning(&MoveToBeginning, window, cx);
18331        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18332        editor.move_to_end(&MoveToEnd, window, cx);
18333        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18334        // Disabling a breakpoint that doesn't exist should do nothing
18335        editor.move_up(&MoveUp, window, cx);
18336        editor.move_up(&MoveUp, window, cx);
18337        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18338    });
18339
18340    let breakpoints = editor.update(cx, |editor, cx| {
18341        editor
18342            .breakpoint_store()
18343            .as_ref()
18344            .unwrap()
18345            .read(cx)
18346            .all_breakpoints(cx)
18347            .clone()
18348    });
18349
18350    let disable_breakpoint = {
18351        let mut bp = Breakpoint::new_standard();
18352        bp.state = BreakpointState::Disabled;
18353        bp
18354    };
18355
18356    assert_eq!(1, breakpoints.len());
18357    assert_breakpoint(
18358        &breakpoints,
18359        &abs_path,
18360        vec![
18361            (0, disable_breakpoint.clone()),
18362            (2, Breakpoint::new_standard()),
18363            (3, disable_breakpoint.clone()),
18364        ],
18365    );
18366
18367    editor.update_in(cx, |editor, window, cx| {
18368        editor.move_to_beginning(&MoveToBeginning, window, cx);
18369        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18370        editor.move_to_end(&MoveToEnd, window, cx);
18371        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18372        editor.move_up(&MoveUp, window, cx);
18373        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18374    });
18375
18376    let breakpoints = editor.update(cx, |editor, cx| {
18377        editor
18378            .breakpoint_store()
18379            .as_ref()
18380            .unwrap()
18381            .read(cx)
18382            .all_breakpoints(cx)
18383            .clone()
18384    });
18385
18386    assert_eq!(1, breakpoints.len());
18387    assert_breakpoint(
18388        &breakpoints,
18389        &abs_path,
18390        vec![
18391            (0, Breakpoint::new_standard()),
18392            (2, disable_breakpoint),
18393            (3, Breakpoint::new_standard()),
18394        ],
18395    );
18396}
18397
18398#[gpui::test]
18399async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18400    init_test(cx, |_| {});
18401    let capabilities = lsp::ServerCapabilities {
18402        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18403            prepare_provider: Some(true),
18404            work_done_progress_options: Default::default(),
18405        })),
18406        ..Default::default()
18407    };
18408    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18409
18410    cx.set_state(indoc! {"
18411        struct Fˇoo {}
18412    "});
18413
18414    cx.update_editor(|editor, _, cx| {
18415        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18416        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18417        editor.highlight_background::<DocumentHighlightRead>(
18418            &[highlight_range],
18419            |c| c.editor_document_highlight_read_background,
18420            cx,
18421        );
18422    });
18423
18424    let mut prepare_rename_handler = cx
18425        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18426            move |_, _, _| async move {
18427                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18428                    start: lsp::Position {
18429                        line: 0,
18430                        character: 7,
18431                    },
18432                    end: lsp::Position {
18433                        line: 0,
18434                        character: 10,
18435                    },
18436                })))
18437            },
18438        );
18439    let prepare_rename_task = cx
18440        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18441        .expect("Prepare rename was not started");
18442    prepare_rename_handler.next().await.unwrap();
18443    prepare_rename_task.await.expect("Prepare rename failed");
18444
18445    let mut rename_handler =
18446        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18447            let edit = lsp::TextEdit {
18448                range: lsp::Range {
18449                    start: lsp::Position {
18450                        line: 0,
18451                        character: 7,
18452                    },
18453                    end: lsp::Position {
18454                        line: 0,
18455                        character: 10,
18456                    },
18457                },
18458                new_text: "FooRenamed".to_string(),
18459            };
18460            Ok(Some(lsp::WorkspaceEdit::new(
18461                // Specify the same edit twice
18462                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18463            )))
18464        });
18465    let rename_task = cx
18466        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18467        .expect("Confirm rename was not started");
18468    rename_handler.next().await.unwrap();
18469    rename_task.await.expect("Confirm rename failed");
18470    cx.run_until_parked();
18471
18472    // Despite two edits, only one is actually applied as those are identical
18473    cx.assert_editor_state(indoc! {"
18474        struct FooRenamedˇ {}
18475    "});
18476}
18477
18478#[gpui::test]
18479async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18480    init_test(cx, |_| {});
18481    // These capabilities indicate that the server does not support prepare rename.
18482    let capabilities = lsp::ServerCapabilities {
18483        rename_provider: Some(lsp::OneOf::Left(true)),
18484        ..Default::default()
18485    };
18486    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18487
18488    cx.set_state(indoc! {"
18489        struct Fˇoo {}
18490    "});
18491
18492    cx.update_editor(|editor, _window, cx| {
18493        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18494        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18495        editor.highlight_background::<DocumentHighlightRead>(
18496            &[highlight_range],
18497            |c| c.editor_document_highlight_read_background,
18498            cx,
18499        );
18500    });
18501
18502    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18503        .expect("Prepare rename was not started")
18504        .await
18505        .expect("Prepare rename failed");
18506
18507    let mut rename_handler =
18508        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18509            let edit = lsp::TextEdit {
18510                range: lsp::Range {
18511                    start: lsp::Position {
18512                        line: 0,
18513                        character: 7,
18514                    },
18515                    end: lsp::Position {
18516                        line: 0,
18517                        character: 10,
18518                    },
18519                },
18520                new_text: "FooRenamed".to_string(),
18521            };
18522            Ok(Some(lsp::WorkspaceEdit::new(
18523                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18524            )))
18525        });
18526    let rename_task = cx
18527        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18528        .expect("Confirm rename was not started");
18529    rename_handler.next().await.unwrap();
18530    rename_task.await.expect("Confirm rename failed");
18531    cx.run_until_parked();
18532
18533    // Correct range is renamed, as `surrounding_word` is used to find it.
18534    cx.assert_editor_state(indoc! {"
18535        struct FooRenamedˇ {}
18536    "});
18537}
18538
18539#[gpui::test]
18540async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18541    init_test(cx, |_| {});
18542    let mut cx = EditorTestContext::new(cx).await;
18543
18544    let language = Arc::new(
18545        Language::new(
18546            LanguageConfig::default(),
18547            Some(tree_sitter_html::LANGUAGE.into()),
18548        )
18549        .with_brackets_query(
18550            r#"
18551            ("<" @open "/>" @close)
18552            ("</" @open ">" @close)
18553            ("<" @open ">" @close)
18554            ("\"" @open "\"" @close)
18555            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18556        "#,
18557        )
18558        .unwrap(),
18559    );
18560    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18561
18562    cx.set_state(indoc! {"
18563        <span>ˇ</span>
18564    "});
18565    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18566    cx.assert_editor_state(indoc! {"
18567        <span>
18568        ˇ
18569        </span>
18570    "});
18571
18572    cx.set_state(indoc! {"
18573        <span><span></span>ˇ</span>
18574    "});
18575    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18576    cx.assert_editor_state(indoc! {"
18577        <span><span></span>
18578        ˇ</span>
18579    "});
18580
18581    cx.set_state(indoc! {"
18582        <span>ˇ
18583        </span>
18584    "});
18585    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18586    cx.assert_editor_state(indoc! {"
18587        <span>
18588        ˇ
18589        </span>
18590    "});
18591}
18592
18593#[gpui::test(iterations = 10)]
18594async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18595    init_test(cx, |_| {});
18596
18597    let fs = FakeFs::new(cx.executor());
18598    fs.insert_tree(
18599        path!("/dir"),
18600        json!({
18601            "a.ts": "a",
18602        }),
18603    )
18604    .await;
18605
18606    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18607    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18608    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18609
18610    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18611    language_registry.add(Arc::new(Language::new(
18612        LanguageConfig {
18613            name: "TypeScript".into(),
18614            matcher: LanguageMatcher {
18615                path_suffixes: vec!["ts".to_string()],
18616                ..Default::default()
18617            },
18618            ..Default::default()
18619        },
18620        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18621    )));
18622    let mut fake_language_servers = language_registry.register_fake_lsp(
18623        "TypeScript",
18624        FakeLspAdapter {
18625            capabilities: lsp::ServerCapabilities {
18626                code_lens_provider: Some(lsp::CodeLensOptions {
18627                    resolve_provider: Some(true),
18628                }),
18629                execute_command_provider: Some(lsp::ExecuteCommandOptions {
18630                    commands: vec!["_the/command".to_string()],
18631                    ..lsp::ExecuteCommandOptions::default()
18632                }),
18633                ..lsp::ServerCapabilities::default()
18634            },
18635            ..FakeLspAdapter::default()
18636        },
18637    );
18638
18639    let (buffer, _handle) = project
18640        .update(cx, |p, cx| {
18641            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18642        })
18643        .await
18644        .unwrap();
18645    cx.executor().run_until_parked();
18646
18647    let fake_server = fake_language_servers.next().await.unwrap();
18648
18649    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
18650    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
18651    drop(buffer_snapshot);
18652    let actions = cx
18653        .update_window(*workspace, |_, window, cx| {
18654            project.code_actions(&buffer, anchor..anchor, window, cx)
18655        })
18656        .unwrap();
18657
18658    fake_server
18659        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
18660            Ok(Some(vec![
18661                lsp::CodeLens {
18662                    range: lsp::Range::default(),
18663                    command: Some(lsp::Command {
18664                        title: "Code lens command".to_owned(),
18665                        command: "_the/command".to_owned(),
18666                        arguments: None,
18667                    }),
18668                    data: None,
18669                },
18670                lsp::CodeLens {
18671                    range: lsp::Range::default(),
18672                    command: Some(lsp::Command {
18673                        title: "Command not in capabilities".to_owned(),
18674                        command: "not in capabilities".to_owned(),
18675                        arguments: None,
18676                    }),
18677                    data: None,
18678                },
18679                lsp::CodeLens {
18680                    range: lsp::Range {
18681                        start: lsp::Position {
18682                            line: 1,
18683                            character: 1,
18684                        },
18685                        end: lsp::Position {
18686                            line: 1,
18687                            character: 1,
18688                        },
18689                    },
18690                    command: Some(lsp::Command {
18691                        title: "Command not in range".to_owned(),
18692                        command: "_the/command".to_owned(),
18693                        arguments: None,
18694                    }),
18695                    data: None,
18696                },
18697            ]))
18698        })
18699        .next()
18700        .await;
18701
18702    let actions = actions.await.unwrap();
18703    assert_eq!(
18704        actions.len(),
18705        1,
18706        "Should have only one valid action for the 0..0 range"
18707    );
18708    let action = actions[0].clone();
18709    let apply = project.update(cx, |project, cx| {
18710        project.apply_code_action(buffer.clone(), action, true, cx)
18711    });
18712
18713    // Resolving the code action does not populate its edits. In absence of
18714    // edits, we must execute the given command.
18715    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
18716        |mut lens, _| async move {
18717            let lens_command = lens.command.as_mut().expect("should have a command");
18718            assert_eq!(lens_command.title, "Code lens command");
18719            lens_command.arguments = Some(vec![json!("the-argument")]);
18720            Ok(lens)
18721        },
18722    );
18723
18724    // While executing the command, the language server sends the editor
18725    // a `workspaceEdit` request.
18726    fake_server
18727        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
18728            let fake = fake_server.clone();
18729            move |params, _| {
18730                assert_eq!(params.command, "_the/command");
18731                let fake = fake.clone();
18732                async move {
18733                    fake.server
18734                        .request::<lsp::request::ApplyWorkspaceEdit>(
18735                            lsp::ApplyWorkspaceEditParams {
18736                                label: None,
18737                                edit: lsp::WorkspaceEdit {
18738                                    changes: Some(
18739                                        [(
18740                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
18741                                            vec![lsp::TextEdit {
18742                                                range: lsp::Range::new(
18743                                                    lsp::Position::new(0, 0),
18744                                                    lsp::Position::new(0, 0),
18745                                                ),
18746                                                new_text: "X".into(),
18747                                            }],
18748                                        )]
18749                                        .into_iter()
18750                                        .collect(),
18751                                    ),
18752                                    ..Default::default()
18753                                },
18754                            },
18755                        )
18756                        .await
18757                        .unwrap();
18758                    Ok(Some(json!(null)))
18759                }
18760            }
18761        })
18762        .next()
18763        .await;
18764
18765    // Applying the code lens command returns a project transaction containing the edits
18766    // sent by the language server in its `workspaceEdit` request.
18767    let transaction = apply.await.unwrap();
18768    assert!(transaction.0.contains_key(&buffer));
18769    buffer.update(cx, |buffer, cx| {
18770        assert_eq!(buffer.text(), "Xa");
18771        buffer.undo(cx);
18772        assert_eq!(buffer.text(), "a");
18773    });
18774}
18775
18776#[gpui::test]
18777async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
18778    init_test(cx, |_| {});
18779
18780    let fs = FakeFs::new(cx.executor());
18781    let main_text = r#"fn main() {
18782println!("1");
18783println!("2");
18784println!("3");
18785println!("4");
18786println!("5");
18787}"#;
18788    let lib_text = "mod foo {}";
18789    fs.insert_tree(
18790        path!("/a"),
18791        json!({
18792            "lib.rs": lib_text,
18793            "main.rs": main_text,
18794        }),
18795    )
18796    .await;
18797
18798    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18799    let (workspace, cx) =
18800        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18801    let worktree_id = workspace.update(cx, |workspace, cx| {
18802        workspace.project().update(cx, |project, cx| {
18803            project.worktrees(cx).next().unwrap().read(cx).id()
18804        })
18805    });
18806
18807    let expected_ranges = vec![
18808        Point::new(0, 0)..Point::new(0, 0),
18809        Point::new(1, 0)..Point::new(1, 1),
18810        Point::new(2, 0)..Point::new(2, 2),
18811        Point::new(3, 0)..Point::new(3, 3),
18812    ];
18813
18814    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
18815    let editor_1 = workspace
18816        .update_in(cx, |workspace, window, cx| {
18817            workspace.open_path(
18818                (worktree_id, "main.rs"),
18819                Some(pane_1.downgrade()),
18820                true,
18821                window,
18822                cx,
18823            )
18824        })
18825        .unwrap()
18826        .await
18827        .downcast::<Editor>()
18828        .unwrap();
18829    pane_1.update(cx, |pane, cx| {
18830        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18831        open_editor.update(cx, |editor, cx| {
18832            assert_eq!(
18833                editor.display_text(cx),
18834                main_text,
18835                "Original main.rs text on initial open",
18836            );
18837            assert_eq!(
18838                editor
18839                    .selections
18840                    .all::<Point>(cx)
18841                    .into_iter()
18842                    .map(|s| s.range())
18843                    .collect::<Vec<_>>(),
18844                vec![Point::zero()..Point::zero()],
18845                "Default selections on initial open",
18846            );
18847        })
18848    });
18849    editor_1.update_in(cx, |editor, window, cx| {
18850        editor.change_selections(None, window, cx, |s| {
18851            s.select_ranges(expected_ranges.clone());
18852        });
18853    });
18854
18855    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
18856        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
18857    });
18858    let editor_2 = workspace
18859        .update_in(cx, |workspace, window, cx| {
18860            workspace.open_path(
18861                (worktree_id, "main.rs"),
18862                Some(pane_2.downgrade()),
18863                true,
18864                window,
18865                cx,
18866            )
18867        })
18868        .unwrap()
18869        .await
18870        .downcast::<Editor>()
18871        .unwrap();
18872    pane_2.update(cx, |pane, cx| {
18873        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18874        open_editor.update(cx, |editor, cx| {
18875            assert_eq!(
18876                editor.display_text(cx),
18877                main_text,
18878                "Original main.rs text on initial open in another panel",
18879            );
18880            assert_eq!(
18881                editor
18882                    .selections
18883                    .all::<Point>(cx)
18884                    .into_iter()
18885                    .map(|s| s.range())
18886                    .collect::<Vec<_>>(),
18887                vec![Point::zero()..Point::zero()],
18888                "Default selections on initial open in another panel",
18889            );
18890        })
18891    });
18892
18893    editor_2.update_in(cx, |editor, window, cx| {
18894        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
18895    });
18896
18897    let _other_editor_1 = workspace
18898        .update_in(cx, |workspace, window, cx| {
18899            workspace.open_path(
18900                (worktree_id, "lib.rs"),
18901                Some(pane_1.downgrade()),
18902                true,
18903                window,
18904                cx,
18905            )
18906        })
18907        .unwrap()
18908        .await
18909        .downcast::<Editor>()
18910        .unwrap();
18911    pane_1
18912        .update_in(cx, |pane, window, cx| {
18913            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
18914                .unwrap()
18915        })
18916        .await
18917        .unwrap();
18918    drop(editor_1);
18919    pane_1.update(cx, |pane, cx| {
18920        pane.active_item()
18921            .unwrap()
18922            .downcast::<Editor>()
18923            .unwrap()
18924            .update(cx, |editor, cx| {
18925                assert_eq!(
18926                    editor.display_text(cx),
18927                    lib_text,
18928                    "Other file should be open and active",
18929                );
18930            });
18931        assert_eq!(pane.items().count(), 1, "No other editors should be open");
18932    });
18933
18934    let _other_editor_2 = workspace
18935        .update_in(cx, |workspace, window, cx| {
18936            workspace.open_path(
18937                (worktree_id, "lib.rs"),
18938                Some(pane_2.downgrade()),
18939                true,
18940                window,
18941                cx,
18942            )
18943        })
18944        .unwrap()
18945        .await
18946        .downcast::<Editor>()
18947        .unwrap();
18948    pane_2
18949        .update_in(cx, |pane, window, cx| {
18950            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
18951                .unwrap()
18952        })
18953        .await
18954        .unwrap();
18955    drop(editor_2);
18956    pane_2.update(cx, |pane, cx| {
18957        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18958        open_editor.update(cx, |editor, cx| {
18959            assert_eq!(
18960                editor.display_text(cx),
18961                lib_text,
18962                "Other file should be open and active in another panel too",
18963            );
18964        });
18965        assert_eq!(
18966            pane.items().count(),
18967            1,
18968            "No other editors should be open in another pane",
18969        );
18970    });
18971
18972    let _editor_1_reopened = workspace
18973        .update_in(cx, |workspace, window, cx| {
18974            workspace.open_path(
18975                (worktree_id, "main.rs"),
18976                Some(pane_1.downgrade()),
18977                true,
18978                window,
18979                cx,
18980            )
18981        })
18982        .unwrap()
18983        .await
18984        .downcast::<Editor>()
18985        .unwrap();
18986    let _editor_2_reopened = workspace
18987        .update_in(cx, |workspace, window, cx| {
18988            workspace.open_path(
18989                (worktree_id, "main.rs"),
18990                Some(pane_2.downgrade()),
18991                true,
18992                window,
18993                cx,
18994            )
18995        })
18996        .unwrap()
18997        .await
18998        .downcast::<Editor>()
18999        .unwrap();
19000    pane_1.update(cx, |pane, cx| {
19001        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19002        open_editor.update(cx, |editor, cx| {
19003            assert_eq!(
19004                editor.display_text(cx),
19005                main_text,
19006                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19007            );
19008            assert_eq!(
19009                editor
19010                    .selections
19011                    .all::<Point>(cx)
19012                    .into_iter()
19013                    .map(|s| s.range())
19014                    .collect::<Vec<_>>(),
19015                expected_ranges,
19016                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19017            );
19018        })
19019    });
19020    pane_2.update(cx, |pane, cx| {
19021        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19022        open_editor.update(cx, |editor, cx| {
19023            assert_eq!(
19024                editor.display_text(cx),
19025                r#"fn main() {
19026⋯rintln!("1");
19027⋯intln!("2");
19028⋯ntln!("3");
19029println!("4");
19030println!("5");
19031}"#,
19032                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19033            );
19034            assert_eq!(
19035                editor
19036                    .selections
19037                    .all::<Point>(cx)
19038                    .into_iter()
19039                    .map(|s| s.range())
19040                    .collect::<Vec<_>>(),
19041                vec![Point::zero()..Point::zero()],
19042                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19043            );
19044        })
19045    });
19046}
19047
19048#[gpui::test]
19049async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19050    init_test(cx, |_| {});
19051
19052    let fs = FakeFs::new(cx.executor());
19053    let main_text = r#"fn main() {
19054println!("1");
19055println!("2");
19056println!("3");
19057println!("4");
19058println!("5");
19059}"#;
19060    let lib_text = "mod foo {}";
19061    fs.insert_tree(
19062        path!("/a"),
19063        json!({
19064            "lib.rs": lib_text,
19065            "main.rs": main_text,
19066        }),
19067    )
19068    .await;
19069
19070    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19071    let (workspace, cx) =
19072        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19073    let worktree_id = workspace.update(cx, |workspace, cx| {
19074        workspace.project().update(cx, |project, cx| {
19075            project.worktrees(cx).next().unwrap().read(cx).id()
19076        })
19077    });
19078
19079    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19080    let editor = workspace
19081        .update_in(cx, |workspace, window, cx| {
19082            workspace.open_path(
19083                (worktree_id, "main.rs"),
19084                Some(pane.downgrade()),
19085                true,
19086                window,
19087                cx,
19088            )
19089        })
19090        .unwrap()
19091        .await
19092        .downcast::<Editor>()
19093        .unwrap();
19094    pane.update(cx, |pane, cx| {
19095        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19096        open_editor.update(cx, |editor, cx| {
19097            assert_eq!(
19098                editor.display_text(cx),
19099                main_text,
19100                "Original main.rs text on initial open",
19101            );
19102        })
19103    });
19104    editor.update_in(cx, |editor, window, cx| {
19105        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19106    });
19107
19108    cx.update_global(|store: &mut SettingsStore, cx| {
19109        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19110            s.restore_on_file_reopen = Some(false);
19111        });
19112    });
19113    editor.update_in(cx, |editor, window, cx| {
19114        editor.fold_ranges(
19115            vec![
19116                Point::new(1, 0)..Point::new(1, 1),
19117                Point::new(2, 0)..Point::new(2, 2),
19118                Point::new(3, 0)..Point::new(3, 3),
19119            ],
19120            false,
19121            window,
19122            cx,
19123        );
19124    });
19125    pane.update_in(cx, |pane, window, cx| {
19126        pane.close_all_items(&CloseAllItems::default(), window, cx)
19127            .unwrap()
19128    })
19129    .await
19130    .unwrap();
19131    pane.update(cx, |pane, _| {
19132        assert!(pane.active_item().is_none());
19133    });
19134    cx.update_global(|store: &mut SettingsStore, cx| {
19135        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19136            s.restore_on_file_reopen = Some(true);
19137        });
19138    });
19139
19140    let _editor_reopened = workspace
19141        .update_in(cx, |workspace, window, cx| {
19142            workspace.open_path(
19143                (worktree_id, "main.rs"),
19144                Some(pane.downgrade()),
19145                true,
19146                window,
19147                cx,
19148            )
19149        })
19150        .unwrap()
19151        .await
19152        .downcast::<Editor>()
19153        .unwrap();
19154    pane.update(cx, |pane, cx| {
19155        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19156        open_editor.update(cx, |editor, cx| {
19157            assert_eq!(
19158                editor.display_text(cx),
19159                main_text,
19160                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19161            );
19162        })
19163    });
19164}
19165
19166fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19167    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19168    point..point
19169}
19170
19171fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19172    let (text, ranges) = marked_text_ranges(marked_text, true);
19173    assert_eq!(editor.text(cx), text);
19174    assert_eq!(
19175        editor.selections.ranges(cx),
19176        ranges,
19177        "Assert selections are {}",
19178        marked_text
19179    );
19180}
19181
19182pub fn handle_signature_help_request(
19183    cx: &mut EditorLspTestContext,
19184    mocked_response: lsp::SignatureHelp,
19185) -> impl Future<Output = ()> + use<> {
19186    let mut request =
19187        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19188            let mocked_response = mocked_response.clone();
19189            async move { Ok(Some(mocked_response)) }
19190        });
19191
19192    async move {
19193        request.next().await;
19194    }
19195}
19196
19197/// Handle completion request passing a marked string specifying where the completion
19198/// should be triggered from using '|' character, what range should be replaced, and what completions
19199/// should be returned using '<' and '>' to delimit the range.
19200///
19201/// Also see `handle_completion_request_with_insert_and_replace`.
19202#[track_caller]
19203pub fn handle_completion_request(
19204    cx: &mut EditorLspTestContext,
19205    marked_string: &str,
19206    completions: Vec<&'static str>,
19207    counter: Arc<AtomicUsize>,
19208) -> impl Future<Output = ()> {
19209    let complete_from_marker: TextRangeMarker = '|'.into();
19210    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19211    let (_, mut marked_ranges) = marked_text_ranges_by(
19212        marked_string,
19213        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19214    );
19215
19216    let complete_from_position =
19217        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19218    let replace_range =
19219        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19220
19221    let mut request =
19222        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19223            let completions = completions.clone();
19224            counter.fetch_add(1, atomic::Ordering::Release);
19225            async move {
19226                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19227                assert_eq!(
19228                    params.text_document_position.position,
19229                    complete_from_position
19230                );
19231                Ok(Some(lsp::CompletionResponse::Array(
19232                    completions
19233                        .iter()
19234                        .map(|completion_text| lsp::CompletionItem {
19235                            label: completion_text.to_string(),
19236                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19237                                range: replace_range,
19238                                new_text: completion_text.to_string(),
19239                            })),
19240                            ..Default::default()
19241                        })
19242                        .collect(),
19243                )))
19244            }
19245        });
19246
19247    async move {
19248        request.next().await;
19249    }
19250}
19251
19252/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19253/// given instead, which also contains an `insert` range.
19254///
19255/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19256/// that is, `replace_range.start..cursor_pos`.
19257pub fn handle_completion_request_with_insert_and_replace(
19258    cx: &mut EditorLspTestContext,
19259    marked_string: &str,
19260    completions: Vec<&'static str>,
19261    counter: Arc<AtomicUsize>,
19262) -> impl Future<Output = ()> {
19263    let complete_from_marker: TextRangeMarker = '|'.into();
19264    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19265    let (_, mut marked_ranges) = marked_text_ranges_by(
19266        marked_string,
19267        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19268    );
19269
19270    let complete_from_position =
19271        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19272    let replace_range =
19273        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19274
19275    let mut request =
19276        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19277            let completions = completions.clone();
19278            counter.fetch_add(1, atomic::Ordering::Release);
19279            async move {
19280                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19281                assert_eq!(
19282                    params.text_document_position.position, complete_from_position,
19283                    "marker `|` position doesn't match",
19284                );
19285                Ok(Some(lsp::CompletionResponse::Array(
19286                    completions
19287                        .iter()
19288                        .map(|completion_text| lsp::CompletionItem {
19289                            label: completion_text.to_string(),
19290                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19291                                lsp::InsertReplaceEdit {
19292                                    insert: lsp::Range {
19293                                        start: replace_range.start,
19294                                        end: complete_from_position,
19295                                    },
19296                                    replace: replace_range,
19297                                    new_text: completion_text.to_string(),
19298                                },
19299                            )),
19300                            ..Default::default()
19301                        })
19302                        .collect(),
19303                )))
19304            }
19305        });
19306
19307    async move {
19308        request.next().await;
19309    }
19310}
19311
19312fn handle_resolve_completion_request(
19313    cx: &mut EditorLspTestContext,
19314    edits: Option<Vec<(&'static str, &'static str)>>,
19315) -> impl Future<Output = ()> {
19316    let edits = edits.map(|edits| {
19317        edits
19318            .iter()
19319            .map(|(marked_string, new_text)| {
19320                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19321                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19322                lsp::TextEdit::new(replace_range, new_text.to_string())
19323            })
19324            .collect::<Vec<_>>()
19325    });
19326
19327    let mut request =
19328        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19329            let edits = edits.clone();
19330            async move {
19331                Ok(lsp::CompletionItem {
19332                    additional_text_edits: edits,
19333                    ..Default::default()
19334                })
19335            }
19336        });
19337
19338    async move {
19339        request.next().await;
19340    }
19341}
19342
19343pub(crate) fn update_test_language_settings(
19344    cx: &mut TestAppContext,
19345    f: impl Fn(&mut AllLanguageSettingsContent),
19346) {
19347    cx.update(|cx| {
19348        SettingsStore::update_global(cx, |store, cx| {
19349            store.update_user_settings::<AllLanguageSettings>(cx, f);
19350        });
19351    });
19352}
19353
19354pub(crate) fn update_test_project_settings(
19355    cx: &mut TestAppContext,
19356    f: impl Fn(&mut ProjectSettings),
19357) {
19358    cx.update(|cx| {
19359        SettingsStore::update_global(cx, |store, cx| {
19360            store.update_user_settings::<ProjectSettings>(cx, f);
19361        });
19362    });
19363}
19364
19365pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19366    cx.update(|cx| {
19367        assets::Assets.load_test_fonts(cx);
19368        let store = SettingsStore::test(cx);
19369        cx.set_global(store);
19370        theme::init(theme::LoadThemes::JustBase, cx);
19371        release_channel::init(SemanticVersion::default(), cx);
19372        client::init_settings(cx);
19373        language::init(cx);
19374        Project::init_settings(cx);
19375        workspace::init_settings(cx);
19376        crate::init(cx);
19377    });
19378
19379    update_test_language_settings(cx, f);
19380}
19381
19382#[track_caller]
19383fn assert_hunk_revert(
19384    not_reverted_text_with_selections: &str,
19385    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19386    expected_reverted_text_with_selections: &str,
19387    base_text: &str,
19388    cx: &mut EditorLspTestContext,
19389) {
19390    cx.set_state(not_reverted_text_with_selections);
19391    cx.set_head_text(base_text);
19392    cx.executor().run_until_parked();
19393
19394    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19395        let snapshot = editor.snapshot(window, cx);
19396        let reverted_hunk_statuses = snapshot
19397            .buffer_snapshot
19398            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19399            .map(|hunk| hunk.status().kind)
19400            .collect::<Vec<_>>();
19401
19402        editor.git_restore(&Default::default(), window, cx);
19403        reverted_hunk_statuses
19404    });
19405    cx.executor().run_until_parked();
19406    cx.assert_editor_state(expected_reverted_text_with_selections);
19407    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19408}