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_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 5878    init_test(cx, |_| {});
 5879
 5880    let mut cx = EditorLspTestContext::new_rust(
 5881        lsp::ServerCapabilities {
 5882            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5883            ..Default::default()
 5884        },
 5885        cx,
 5886    )
 5887    .await;
 5888
 5889    cx.set_state(indoc! {"
 5890        line 1
 5891        line 2
 5892        linˇe 3
 5893        line 4
 5894        line 5
 5895    "});
 5896
 5897    // Make an edit
 5898    cx.update_editor(|editor, window, cx| {
 5899        editor.handle_input("X", window, cx);
 5900    });
 5901
 5902    // Move cursor to a different position
 5903    cx.update_editor(|editor, window, cx| {
 5904        editor.change_selections(None, window, cx, |s| {
 5905            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 5906        });
 5907    });
 5908
 5909    cx.assert_editor_state(indoc! {"
 5910        line 1
 5911        line 2
 5912        linXe 3
 5913        line 4
 5914        liˇne 5
 5915    "});
 5916
 5917    cx.lsp
 5918        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 5919            Ok(Some(vec![lsp::TextEdit::new(
 5920                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 5921                "PREFIX ".to_string(),
 5922            )]))
 5923        });
 5924
 5925    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 5926        .unwrap()
 5927        .await
 5928        .unwrap();
 5929
 5930    cx.assert_editor_state(indoc! {"
 5931        PREFIX line 1
 5932        line 2
 5933        linXe 3
 5934        line 4
 5935        liˇne 5
 5936    "});
 5937
 5938    // Undo formatting
 5939    cx.update_editor(|editor, window, cx| {
 5940        editor.undo(&Default::default(), window, cx);
 5941    });
 5942
 5943    // Verify cursor moved back to position after edit
 5944    cx.assert_editor_state(indoc! {"
 5945        line 1
 5946        line 2
 5947        linXˇe 3
 5948        line 4
 5949        line 5
 5950    "});
 5951}
 5952
 5953#[gpui::test]
 5954async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 5955    init_test(cx, |_| {});
 5956
 5957    let mut cx = EditorTestContext::new(cx).await;
 5958    cx.set_state(
 5959        r#"let foo = 2;
 5960lˇet foo = 2;
 5961let fooˇ = 2;
 5962let foo = 2;
 5963let foo = ˇ2;"#,
 5964    );
 5965
 5966    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5967        .unwrap();
 5968    cx.assert_editor_state(
 5969        r#"let foo = 2;
 5970«letˇ» foo = 2;
 5971let «fooˇ» = 2;
 5972let foo = 2;
 5973let foo = «2ˇ»;"#,
 5974    );
 5975
 5976    // noop for multiple selections with different contents
 5977    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5978        .unwrap();
 5979    cx.assert_editor_state(
 5980        r#"let foo = 2;
 5981«letˇ» foo = 2;
 5982let «fooˇ» = 2;
 5983let foo = 2;
 5984let foo = «2ˇ»;"#,
 5985    );
 5986}
 5987
 5988#[gpui::test]
 5989async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 5990    init_test(cx, |_| {});
 5991
 5992    let mut cx =
 5993        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 5994
 5995    cx.assert_editor_state(indoc! {"
 5996        ˇbbb
 5997        ccc
 5998
 5999        bbb
 6000        ccc
 6001        "});
 6002    cx.dispatch_action(SelectPrevious::default());
 6003    cx.assert_editor_state(indoc! {"
 6004                «bbbˇ»
 6005                ccc
 6006
 6007                bbb
 6008                ccc
 6009                "});
 6010    cx.dispatch_action(SelectPrevious::default());
 6011    cx.assert_editor_state(indoc! {"
 6012                «bbbˇ»
 6013                ccc
 6014
 6015                «bbbˇ»
 6016                ccc
 6017                "});
 6018}
 6019
 6020#[gpui::test]
 6021async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6022    init_test(cx, |_| {});
 6023
 6024    let mut cx = EditorTestContext::new(cx).await;
 6025    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6026
 6027    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6028        .unwrap();
 6029    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6030
 6031    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6032        .unwrap();
 6033    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6034
 6035    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6036    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6037
 6038    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6039    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6040
 6041    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6042        .unwrap();
 6043    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6044
 6045    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6046        .unwrap();
 6047    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 6048
 6049    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6050        .unwrap();
 6051    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 6052}
 6053
 6054#[gpui::test]
 6055async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6056    init_test(cx, |_| {});
 6057
 6058    let mut cx = EditorTestContext::new(cx).await;
 6059    cx.set_state("");
 6060
 6061    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6062        .unwrap();
 6063    cx.assert_editor_state("«aˇ»");
 6064    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6065        .unwrap();
 6066    cx.assert_editor_state("«aˇ»");
 6067}
 6068
 6069#[gpui::test]
 6070async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6071    init_test(cx, |_| {});
 6072
 6073    let mut cx = EditorTestContext::new(cx).await;
 6074    cx.set_state(
 6075        r#"let foo = 2;
 6076lˇet foo = 2;
 6077let fooˇ = 2;
 6078let foo = 2;
 6079let foo = ˇ2;"#,
 6080    );
 6081
 6082    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6083        .unwrap();
 6084    cx.assert_editor_state(
 6085        r#"let foo = 2;
 6086«letˇ» foo = 2;
 6087let «fooˇ» = 2;
 6088let foo = 2;
 6089let foo = «2ˇ»;"#,
 6090    );
 6091
 6092    // noop for multiple selections with different contents
 6093    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6094        .unwrap();
 6095    cx.assert_editor_state(
 6096        r#"let foo = 2;
 6097«letˇ» foo = 2;
 6098let «fooˇ» = 2;
 6099let foo = 2;
 6100let foo = «2ˇ»;"#,
 6101    );
 6102}
 6103
 6104#[gpui::test]
 6105async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6106    init_test(cx, |_| {});
 6107
 6108    let mut cx = EditorTestContext::new(cx).await;
 6109    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6110
 6111    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6112        .unwrap();
 6113    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6114
 6115    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6116        .unwrap();
 6117    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6118
 6119    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6120    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 6121
 6122    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6123    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 6124
 6125    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6126        .unwrap();
 6127    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 6128
 6129    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6130        .unwrap();
 6131    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 6132}
 6133
 6134#[gpui::test]
 6135async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6136    init_test(cx, |_| {});
 6137
 6138    let language = Arc::new(Language::new(
 6139        LanguageConfig::default(),
 6140        Some(tree_sitter_rust::LANGUAGE.into()),
 6141    ));
 6142
 6143    let text = r#"
 6144        use mod1::mod2::{mod3, mod4};
 6145
 6146        fn fn_1(param1: bool, param2: &str) {
 6147            let var1 = "text";
 6148        }
 6149    "#
 6150    .unindent();
 6151
 6152    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6153    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6154    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6155
 6156    editor
 6157        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6158        .await;
 6159
 6160    editor.update_in(cx, |editor, window, cx| {
 6161        editor.change_selections(None, window, cx, |s| {
 6162            s.select_display_ranges([
 6163                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6164                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6165                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6166            ]);
 6167        });
 6168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6169    });
 6170    editor.update(cx, |editor, cx| {
 6171        assert_text_with_selections(
 6172            editor,
 6173            indoc! {r#"
 6174                use mod1::mod2::{mod3, «mod4ˇ»};
 6175
 6176                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6177                    let var1 = "«ˇtext»";
 6178                }
 6179            "#},
 6180            cx,
 6181        );
 6182    });
 6183
 6184    editor.update_in(cx, |editor, window, cx| {
 6185        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6186    });
 6187    editor.update(cx, |editor, cx| {
 6188        assert_text_with_selections(
 6189            editor,
 6190            indoc! {r#"
 6191                use mod1::mod2::«{mod3, mod4}ˇ»;
 6192
 6193                «ˇfn fn_1(param1: bool, param2: &str) {
 6194                    let var1 = "text";
 6195 6196            "#},
 6197            cx,
 6198        );
 6199    });
 6200
 6201    editor.update_in(cx, |editor, window, cx| {
 6202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6203    });
 6204    assert_eq!(
 6205        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6206        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6207    );
 6208
 6209    // Trying to expand the selected syntax node one more time has no effect.
 6210    editor.update_in(cx, |editor, window, cx| {
 6211        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6212    });
 6213    assert_eq!(
 6214        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6215        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6216    );
 6217
 6218    editor.update_in(cx, |editor, window, cx| {
 6219        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6220    });
 6221    editor.update(cx, |editor, cx| {
 6222        assert_text_with_selections(
 6223            editor,
 6224            indoc! {r#"
 6225                use mod1::mod2::«{mod3, mod4}ˇ»;
 6226
 6227                «ˇfn fn_1(param1: bool, param2: &str) {
 6228                    let var1 = "text";
 6229 6230            "#},
 6231            cx,
 6232        );
 6233    });
 6234
 6235    editor.update_in(cx, |editor, window, cx| {
 6236        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6237    });
 6238    editor.update(cx, |editor, cx| {
 6239        assert_text_with_selections(
 6240            editor,
 6241            indoc! {r#"
 6242                use mod1::mod2::{mod3, «mod4ˇ»};
 6243
 6244                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6245                    let var1 = "«ˇtext»";
 6246                }
 6247            "#},
 6248            cx,
 6249        );
 6250    });
 6251
 6252    editor.update_in(cx, |editor, window, cx| {
 6253        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6254    });
 6255    editor.update(cx, |editor, cx| {
 6256        assert_text_with_selections(
 6257            editor,
 6258            indoc! {r#"
 6259                use mod1::mod2::{mod3, mo«ˇ»d4};
 6260
 6261                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6262                    let var1 = "te«ˇ»xt";
 6263                }
 6264            "#},
 6265            cx,
 6266        );
 6267    });
 6268
 6269    // Trying to shrink the selected syntax node one more time has no effect.
 6270    editor.update_in(cx, |editor, window, cx| {
 6271        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6272    });
 6273    editor.update_in(cx, |editor, _, cx| {
 6274        assert_text_with_selections(
 6275            editor,
 6276            indoc! {r#"
 6277                use mod1::mod2::{mod3, mo«ˇ»d4};
 6278
 6279                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6280                    let var1 = "te«ˇ»xt";
 6281                }
 6282            "#},
 6283            cx,
 6284        );
 6285    });
 6286
 6287    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6288    // a fold.
 6289    editor.update_in(cx, |editor, window, cx| {
 6290        editor.fold_creases(
 6291            vec![
 6292                Crease::simple(
 6293                    Point::new(0, 21)..Point::new(0, 24),
 6294                    FoldPlaceholder::test(),
 6295                ),
 6296                Crease::simple(
 6297                    Point::new(3, 20)..Point::new(3, 22),
 6298                    FoldPlaceholder::test(),
 6299                ),
 6300            ],
 6301            true,
 6302            window,
 6303            cx,
 6304        );
 6305        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6306    });
 6307    editor.update(cx, |editor, cx| {
 6308        assert_text_with_selections(
 6309            editor,
 6310            indoc! {r#"
 6311                use mod1::mod2::«{mod3, mod4}ˇ»;
 6312
 6313                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6314                    «ˇlet var1 = "text";»
 6315                }
 6316            "#},
 6317            cx,
 6318        );
 6319    });
 6320}
 6321
 6322#[gpui::test]
 6323async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6324    init_test(cx, |_| {});
 6325
 6326    let base_text = r#"
 6327        impl A {
 6328            // this is an uncommitted comment
 6329
 6330            fn b() {
 6331                c();
 6332            }
 6333
 6334            // this is another uncommitted comment
 6335
 6336            fn d() {
 6337                // e
 6338                // f
 6339            }
 6340        }
 6341
 6342        fn g() {
 6343            // h
 6344        }
 6345    "#
 6346    .unindent();
 6347
 6348    let text = r#"
 6349        ˇimpl A {
 6350
 6351            fn b() {
 6352                c();
 6353            }
 6354
 6355            fn d() {
 6356                // e
 6357                // f
 6358            }
 6359        }
 6360
 6361        fn g() {
 6362            // h
 6363        }
 6364    "#
 6365    .unindent();
 6366
 6367    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6368    cx.set_state(&text);
 6369    cx.set_head_text(&base_text);
 6370    cx.update_editor(|editor, window, cx| {
 6371        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6372    });
 6373
 6374    cx.assert_state_with_diff(
 6375        "
 6376        ˇimpl A {
 6377      -     // this is an uncommitted comment
 6378
 6379            fn b() {
 6380                c();
 6381            }
 6382
 6383      -     // this is another uncommitted comment
 6384      -
 6385            fn d() {
 6386                // e
 6387                // f
 6388            }
 6389        }
 6390
 6391        fn g() {
 6392            // h
 6393        }
 6394    "
 6395        .unindent(),
 6396    );
 6397
 6398    let expected_display_text = "
 6399        impl A {
 6400            // this is an uncommitted comment
 6401
 6402            fn b() {
 6403 6404            }
 6405
 6406            // this is another uncommitted comment
 6407
 6408            fn d() {
 6409 6410            }
 6411        }
 6412
 6413        fn g() {
 6414 6415        }
 6416        "
 6417    .unindent();
 6418
 6419    cx.update_editor(|editor, window, cx| {
 6420        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 6421        assert_eq!(editor.display_text(cx), expected_display_text);
 6422    });
 6423}
 6424
 6425#[gpui::test]
 6426async fn test_autoindent(cx: &mut TestAppContext) {
 6427    init_test(cx, |_| {});
 6428
 6429    let language = Arc::new(
 6430        Language::new(
 6431            LanguageConfig {
 6432                brackets: BracketPairConfig {
 6433                    pairs: vec![
 6434                        BracketPair {
 6435                            start: "{".to_string(),
 6436                            end: "}".to_string(),
 6437                            close: false,
 6438                            surround: false,
 6439                            newline: true,
 6440                        },
 6441                        BracketPair {
 6442                            start: "(".to_string(),
 6443                            end: ")".to_string(),
 6444                            close: false,
 6445                            surround: false,
 6446                            newline: true,
 6447                        },
 6448                    ],
 6449                    ..Default::default()
 6450                },
 6451                ..Default::default()
 6452            },
 6453            Some(tree_sitter_rust::LANGUAGE.into()),
 6454        )
 6455        .with_indents_query(
 6456            r#"
 6457                (_ "(" ")" @end) @indent
 6458                (_ "{" "}" @end) @indent
 6459            "#,
 6460        )
 6461        .unwrap(),
 6462    );
 6463
 6464    let text = "fn a() {}";
 6465
 6466    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6467    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6468    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6469    editor
 6470        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6471        .await;
 6472
 6473    editor.update_in(cx, |editor, window, cx| {
 6474        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 6475        editor.newline(&Newline, window, cx);
 6476        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 6477        assert_eq!(
 6478            editor.selections.ranges(cx),
 6479            &[
 6480                Point::new(1, 4)..Point::new(1, 4),
 6481                Point::new(3, 4)..Point::new(3, 4),
 6482                Point::new(5, 0)..Point::new(5, 0)
 6483            ]
 6484        );
 6485    });
 6486}
 6487
 6488#[gpui::test]
 6489async fn test_autoindent_selections(cx: &mut TestAppContext) {
 6490    init_test(cx, |_| {});
 6491
 6492    {
 6493        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6494        cx.set_state(indoc! {"
 6495            impl A {
 6496
 6497                fn b() {}
 6498
 6499            «fn c() {
 6500
 6501            }ˇ»
 6502            }
 6503        "});
 6504
 6505        cx.update_editor(|editor, window, cx| {
 6506            editor.autoindent(&Default::default(), window, cx);
 6507        });
 6508
 6509        cx.assert_editor_state(indoc! {"
 6510            impl A {
 6511
 6512                fn b() {}
 6513
 6514                «fn c() {
 6515
 6516                }ˇ»
 6517            }
 6518        "});
 6519    }
 6520
 6521    {
 6522        let mut cx = EditorTestContext::new_multibuffer(
 6523            cx,
 6524            [indoc! { "
 6525                impl A {
 6526                «
 6527                // a
 6528                fn b(){}
 6529                »
 6530                «
 6531                    }
 6532                    fn c(){}
 6533                »
 6534            "}],
 6535        );
 6536
 6537        let buffer = cx.update_editor(|editor, _, cx| {
 6538            let buffer = editor.buffer().update(cx, |buffer, _| {
 6539                buffer.all_buffers().iter().next().unwrap().clone()
 6540            });
 6541            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6542            buffer
 6543        });
 6544
 6545        cx.run_until_parked();
 6546        cx.update_editor(|editor, window, cx| {
 6547            editor.select_all(&Default::default(), window, cx);
 6548            editor.autoindent(&Default::default(), window, cx)
 6549        });
 6550        cx.run_until_parked();
 6551
 6552        cx.update(|_, cx| {
 6553            assert_eq!(
 6554                buffer.read(cx).text(),
 6555                indoc! { "
 6556                    impl A {
 6557
 6558                        // a
 6559                        fn b(){}
 6560
 6561
 6562                    }
 6563                    fn c(){}
 6564
 6565                " }
 6566            )
 6567        });
 6568    }
 6569}
 6570
 6571#[gpui::test]
 6572async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 6573    init_test(cx, |_| {});
 6574
 6575    let mut cx = EditorTestContext::new(cx).await;
 6576
 6577    let language = Arc::new(Language::new(
 6578        LanguageConfig {
 6579            brackets: BracketPairConfig {
 6580                pairs: vec![
 6581                    BracketPair {
 6582                        start: "{".to_string(),
 6583                        end: "}".to_string(),
 6584                        close: true,
 6585                        surround: true,
 6586                        newline: true,
 6587                    },
 6588                    BracketPair {
 6589                        start: "(".to_string(),
 6590                        end: ")".to_string(),
 6591                        close: true,
 6592                        surround: true,
 6593                        newline: true,
 6594                    },
 6595                    BracketPair {
 6596                        start: "/*".to_string(),
 6597                        end: " */".to_string(),
 6598                        close: true,
 6599                        surround: true,
 6600                        newline: true,
 6601                    },
 6602                    BracketPair {
 6603                        start: "[".to_string(),
 6604                        end: "]".to_string(),
 6605                        close: false,
 6606                        surround: false,
 6607                        newline: true,
 6608                    },
 6609                    BracketPair {
 6610                        start: "\"".to_string(),
 6611                        end: "\"".to_string(),
 6612                        close: true,
 6613                        surround: true,
 6614                        newline: false,
 6615                    },
 6616                    BracketPair {
 6617                        start: "<".to_string(),
 6618                        end: ">".to_string(),
 6619                        close: false,
 6620                        surround: true,
 6621                        newline: true,
 6622                    },
 6623                ],
 6624                ..Default::default()
 6625            },
 6626            autoclose_before: "})]".to_string(),
 6627            ..Default::default()
 6628        },
 6629        Some(tree_sitter_rust::LANGUAGE.into()),
 6630    ));
 6631
 6632    cx.language_registry().add(language.clone());
 6633    cx.update_buffer(|buffer, cx| {
 6634        buffer.set_language(Some(language), cx);
 6635    });
 6636
 6637    cx.set_state(
 6638        &r#"
 6639            🏀ˇ
 6640            εˇ
 6641            ❤️ˇ
 6642        "#
 6643        .unindent(),
 6644    );
 6645
 6646    // autoclose multiple nested brackets at multiple cursors
 6647    cx.update_editor(|editor, window, cx| {
 6648        editor.handle_input("{", window, cx);
 6649        editor.handle_input("{", window, cx);
 6650        editor.handle_input("{", window, cx);
 6651    });
 6652    cx.assert_editor_state(
 6653        &"
 6654            🏀{{{ˇ}}}
 6655            ε{{{ˇ}}}
 6656            ❤️{{{ˇ}}}
 6657        "
 6658        .unindent(),
 6659    );
 6660
 6661    // insert a different closing bracket
 6662    cx.update_editor(|editor, window, cx| {
 6663        editor.handle_input(")", window, cx);
 6664    });
 6665    cx.assert_editor_state(
 6666        &"
 6667            🏀{{{)ˇ}}}
 6668            ε{{{)ˇ}}}
 6669            ❤️{{{)ˇ}}}
 6670        "
 6671        .unindent(),
 6672    );
 6673
 6674    // skip over the auto-closed brackets when typing a closing bracket
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.move_right(&MoveRight, window, cx);
 6677        editor.handle_input("}", window, cx);
 6678        editor.handle_input("}", window, cx);
 6679        editor.handle_input("}", window, cx);
 6680    });
 6681    cx.assert_editor_state(
 6682        &"
 6683            🏀{{{)}}}}ˇ
 6684            ε{{{)}}}}ˇ
 6685            ❤️{{{)}}}}ˇ
 6686        "
 6687        .unindent(),
 6688    );
 6689
 6690    // autoclose multi-character pairs
 6691    cx.set_state(
 6692        &"
 6693            ˇ
 6694            ˇ
 6695        "
 6696        .unindent(),
 6697    );
 6698    cx.update_editor(|editor, window, cx| {
 6699        editor.handle_input("/", window, cx);
 6700        editor.handle_input("*", window, cx);
 6701    });
 6702    cx.assert_editor_state(
 6703        &"
 6704            /*ˇ */
 6705            /*ˇ */
 6706        "
 6707        .unindent(),
 6708    );
 6709
 6710    // one cursor autocloses a multi-character pair, one cursor
 6711    // does not autoclose.
 6712    cx.set_state(
 6713        &"
 6714 6715            ˇ
 6716        "
 6717        .unindent(),
 6718    );
 6719    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 6720    cx.assert_editor_state(
 6721        &"
 6722            /*ˇ */
 6723 6724        "
 6725        .unindent(),
 6726    );
 6727
 6728    // Don't autoclose if the next character isn't whitespace and isn't
 6729    // listed in the language's "autoclose_before" section.
 6730    cx.set_state("ˇa b");
 6731    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6732    cx.assert_editor_state("{ˇa b");
 6733
 6734    // Don't autoclose if `close` is false for the bracket pair
 6735    cx.set_state("ˇ");
 6736    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 6737    cx.assert_editor_state("");
 6738
 6739    // Surround with brackets if text is selected
 6740    cx.set_state("«aˇ» b");
 6741    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6742    cx.assert_editor_state("{«aˇ»} b");
 6743
 6744    // Autoclose when not immediately after a word character
 6745    cx.set_state("a ˇ");
 6746    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6747    cx.assert_editor_state("a \"ˇ\"");
 6748
 6749    // Autoclose pair where the start and end characters are the same
 6750    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6751    cx.assert_editor_state("a \"\"ˇ");
 6752
 6753    // Don't autoclose when immediately after a word character
 6754    cx.set_state("");
 6755    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6756    cx.assert_editor_state("a\"ˇ");
 6757
 6758    // Do autoclose when after a non-word character
 6759    cx.set_state("");
 6760    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6761    cx.assert_editor_state("{\"ˇ\"");
 6762
 6763    // Non identical pairs autoclose regardless of preceding character
 6764    cx.set_state("");
 6765    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6766    cx.assert_editor_state("a{ˇ}");
 6767
 6768    // Don't autoclose pair if autoclose is disabled
 6769    cx.set_state("ˇ");
 6770    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6771    cx.assert_editor_state("");
 6772
 6773    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 6774    cx.set_state("«aˇ» b");
 6775    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6776    cx.assert_editor_state("<«aˇ»> b");
 6777}
 6778
 6779#[gpui::test]
 6780async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 6781    init_test(cx, |settings| {
 6782        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 6783    });
 6784
 6785    let mut cx = EditorTestContext::new(cx).await;
 6786
 6787    let language = Arc::new(Language::new(
 6788        LanguageConfig {
 6789            brackets: BracketPairConfig {
 6790                pairs: vec![
 6791                    BracketPair {
 6792                        start: "{".to_string(),
 6793                        end: "}".to_string(),
 6794                        close: true,
 6795                        surround: true,
 6796                        newline: true,
 6797                    },
 6798                    BracketPair {
 6799                        start: "(".to_string(),
 6800                        end: ")".to_string(),
 6801                        close: true,
 6802                        surround: true,
 6803                        newline: true,
 6804                    },
 6805                    BracketPair {
 6806                        start: "[".to_string(),
 6807                        end: "]".to_string(),
 6808                        close: false,
 6809                        surround: false,
 6810                        newline: true,
 6811                    },
 6812                ],
 6813                ..Default::default()
 6814            },
 6815            autoclose_before: "})]".to_string(),
 6816            ..Default::default()
 6817        },
 6818        Some(tree_sitter_rust::LANGUAGE.into()),
 6819    ));
 6820
 6821    cx.language_registry().add(language.clone());
 6822    cx.update_buffer(|buffer, cx| {
 6823        buffer.set_language(Some(language), cx);
 6824    });
 6825
 6826    cx.set_state(
 6827        &"
 6828            ˇ
 6829            ˇ
 6830            ˇ
 6831        "
 6832        .unindent(),
 6833    );
 6834
 6835    // ensure only matching closing brackets are skipped over
 6836    cx.update_editor(|editor, window, cx| {
 6837        editor.handle_input("}", window, cx);
 6838        editor.move_left(&MoveLeft, window, cx);
 6839        editor.handle_input(")", window, cx);
 6840        editor.move_left(&MoveLeft, window, cx);
 6841    });
 6842    cx.assert_editor_state(
 6843        &"
 6844            ˇ)}
 6845            ˇ)}
 6846            ˇ)}
 6847        "
 6848        .unindent(),
 6849    );
 6850
 6851    // skip-over closing brackets at multiple cursors
 6852    cx.update_editor(|editor, window, cx| {
 6853        editor.handle_input(")", window, cx);
 6854        editor.handle_input("}", window, cx);
 6855    });
 6856    cx.assert_editor_state(
 6857        &"
 6858            )}ˇ
 6859            )}ˇ
 6860            )}ˇ
 6861        "
 6862        .unindent(),
 6863    );
 6864
 6865    // ignore non-close brackets
 6866    cx.update_editor(|editor, window, cx| {
 6867        editor.handle_input("]", window, cx);
 6868        editor.move_left(&MoveLeft, window, cx);
 6869        editor.handle_input("]", window, cx);
 6870    });
 6871    cx.assert_editor_state(
 6872        &"
 6873            )}]ˇ]
 6874            )}]ˇ]
 6875            )}]ˇ]
 6876        "
 6877        .unindent(),
 6878    );
 6879}
 6880
 6881#[gpui::test]
 6882async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 6883    init_test(cx, |_| {});
 6884
 6885    let mut cx = EditorTestContext::new(cx).await;
 6886
 6887    let html_language = Arc::new(
 6888        Language::new(
 6889            LanguageConfig {
 6890                name: "HTML".into(),
 6891                brackets: BracketPairConfig {
 6892                    pairs: vec![
 6893                        BracketPair {
 6894                            start: "<".into(),
 6895                            end: ">".into(),
 6896                            close: true,
 6897                            ..Default::default()
 6898                        },
 6899                        BracketPair {
 6900                            start: "{".into(),
 6901                            end: "}".into(),
 6902                            close: true,
 6903                            ..Default::default()
 6904                        },
 6905                        BracketPair {
 6906                            start: "(".into(),
 6907                            end: ")".into(),
 6908                            close: true,
 6909                            ..Default::default()
 6910                        },
 6911                    ],
 6912                    ..Default::default()
 6913                },
 6914                autoclose_before: "})]>".into(),
 6915                ..Default::default()
 6916            },
 6917            Some(tree_sitter_html::LANGUAGE.into()),
 6918        )
 6919        .with_injection_query(
 6920            r#"
 6921            (script_element
 6922                (raw_text) @injection.content
 6923                (#set! injection.language "javascript"))
 6924            "#,
 6925        )
 6926        .unwrap(),
 6927    );
 6928
 6929    let javascript_language = Arc::new(Language::new(
 6930        LanguageConfig {
 6931            name: "JavaScript".into(),
 6932            brackets: BracketPairConfig {
 6933                pairs: vec![
 6934                    BracketPair {
 6935                        start: "/*".into(),
 6936                        end: " */".into(),
 6937                        close: true,
 6938                        ..Default::default()
 6939                    },
 6940                    BracketPair {
 6941                        start: "{".into(),
 6942                        end: "}".into(),
 6943                        close: true,
 6944                        ..Default::default()
 6945                    },
 6946                    BracketPair {
 6947                        start: "(".into(),
 6948                        end: ")".into(),
 6949                        close: true,
 6950                        ..Default::default()
 6951                    },
 6952                ],
 6953                ..Default::default()
 6954            },
 6955            autoclose_before: "})]>".into(),
 6956            ..Default::default()
 6957        },
 6958        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 6959    ));
 6960
 6961    cx.language_registry().add(html_language.clone());
 6962    cx.language_registry().add(javascript_language.clone());
 6963
 6964    cx.update_buffer(|buffer, cx| {
 6965        buffer.set_language(Some(html_language), cx);
 6966    });
 6967
 6968    cx.set_state(
 6969        &r#"
 6970            <body>ˇ
 6971                <script>
 6972                    var x = 1;ˇ
 6973                </script>
 6974            </body>ˇ
 6975        "#
 6976        .unindent(),
 6977    );
 6978
 6979    // Precondition: different languages are active at different locations.
 6980    cx.update_editor(|editor, window, cx| {
 6981        let snapshot = editor.snapshot(window, cx);
 6982        let cursors = editor.selections.ranges::<usize>(cx);
 6983        let languages = cursors
 6984            .iter()
 6985            .map(|c| snapshot.language_at(c.start).unwrap().name())
 6986            .collect::<Vec<_>>();
 6987        assert_eq!(
 6988            languages,
 6989            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 6990        );
 6991    });
 6992
 6993    // Angle brackets autoclose in HTML, but not JavaScript.
 6994    cx.update_editor(|editor, window, cx| {
 6995        editor.handle_input("<", window, cx);
 6996        editor.handle_input("a", window, cx);
 6997    });
 6998    cx.assert_editor_state(
 6999        &r#"
 7000            <body><aˇ>
 7001                <script>
 7002                    var x = 1;<aˇ
 7003                </script>
 7004            </body><aˇ>
 7005        "#
 7006        .unindent(),
 7007    );
 7008
 7009    // Curly braces and parens autoclose in both HTML and JavaScript.
 7010    cx.update_editor(|editor, window, cx| {
 7011        editor.handle_input(" b=", window, cx);
 7012        editor.handle_input("{", window, cx);
 7013        editor.handle_input("c", window, cx);
 7014        editor.handle_input("(", window, cx);
 7015    });
 7016    cx.assert_editor_state(
 7017        &r#"
 7018            <body><a b={c(ˇ)}>
 7019                <script>
 7020                    var x = 1;<a b={c(ˇ)}
 7021                </script>
 7022            </body><a b={c(ˇ)}>
 7023        "#
 7024        .unindent(),
 7025    );
 7026
 7027    // Brackets that were already autoclosed are skipped.
 7028    cx.update_editor(|editor, window, cx| {
 7029        editor.handle_input(")", window, cx);
 7030        editor.handle_input("d", window, cx);
 7031        editor.handle_input("}", window, cx);
 7032    });
 7033    cx.assert_editor_state(
 7034        &r#"
 7035            <body><a b={c()d}ˇ>
 7036                <script>
 7037                    var x = 1;<a b={c()d}ˇ
 7038                </script>
 7039            </body><a b={c()d}ˇ>
 7040        "#
 7041        .unindent(),
 7042    );
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.handle_input(">", window, cx);
 7045    });
 7046    cx.assert_editor_state(
 7047        &r#"
 7048            <body><a b={c()d}>ˇ
 7049                <script>
 7050                    var x = 1;<a b={c()d}>ˇ
 7051                </script>
 7052            </body><a b={c()d}>ˇ
 7053        "#
 7054        .unindent(),
 7055    );
 7056
 7057    // Reset
 7058    cx.set_state(
 7059        &r#"
 7060            <body>ˇ
 7061                <script>
 7062                    var x = 1;ˇ
 7063                </script>
 7064            </body>ˇ
 7065        "#
 7066        .unindent(),
 7067    );
 7068
 7069    cx.update_editor(|editor, window, cx| {
 7070        editor.handle_input("<", window, cx);
 7071    });
 7072    cx.assert_editor_state(
 7073        &r#"
 7074            <body><ˇ>
 7075                <script>
 7076                    var x = 1;<ˇ
 7077                </script>
 7078            </body><ˇ>
 7079        "#
 7080        .unindent(),
 7081    );
 7082
 7083    // When backspacing, the closing angle brackets are removed.
 7084    cx.update_editor(|editor, window, cx| {
 7085        editor.backspace(&Backspace, window, cx);
 7086    });
 7087    cx.assert_editor_state(
 7088        &r#"
 7089            <body>ˇ
 7090                <script>
 7091                    var x = 1;ˇ
 7092                </script>
 7093            </body>ˇ
 7094        "#
 7095        .unindent(),
 7096    );
 7097
 7098    // Block comments autoclose in JavaScript, but not HTML.
 7099    cx.update_editor(|editor, window, cx| {
 7100        editor.handle_input("/", window, cx);
 7101        editor.handle_input("*", window, cx);
 7102    });
 7103    cx.assert_editor_state(
 7104        &r#"
 7105            <body>/*ˇ
 7106                <script>
 7107                    var x = 1;/*ˇ */
 7108                </script>
 7109            </body>/*ˇ
 7110        "#
 7111        .unindent(),
 7112    );
 7113}
 7114
 7115#[gpui::test]
 7116async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7117    init_test(cx, |_| {});
 7118
 7119    let mut cx = EditorTestContext::new(cx).await;
 7120
 7121    let rust_language = Arc::new(
 7122        Language::new(
 7123            LanguageConfig {
 7124                name: "Rust".into(),
 7125                brackets: serde_json::from_value(json!([
 7126                    { "start": "{", "end": "}", "close": true, "newline": true },
 7127                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7128                ]))
 7129                .unwrap(),
 7130                autoclose_before: "})]>".into(),
 7131                ..Default::default()
 7132            },
 7133            Some(tree_sitter_rust::LANGUAGE.into()),
 7134        )
 7135        .with_override_query("(string_literal) @string")
 7136        .unwrap(),
 7137    );
 7138
 7139    cx.language_registry().add(rust_language.clone());
 7140    cx.update_buffer(|buffer, cx| {
 7141        buffer.set_language(Some(rust_language), cx);
 7142    });
 7143
 7144    cx.set_state(
 7145        &r#"
 7146            let x = ˇ
 7147        "#
 7148        .unindent(),
 7149    );
 7150
 7151    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7152    cx.update_editor(|editor, window, cx| {
 7153        editor.handle_input("\"", window, cx);
 7154    });
 7155    cx.assert_editor_state(
 7156        &r#"
 7157            let x = "ˇ"
 7158        "#
 7159        .unindent(),
 7160    );
 7161
 7162    // Inserting another quotation mark. The cursor moves across the existing
 7163    // automatically-inserted quotation mark.
 7164    cx.update_editor(|editor, window, cx| {
 7165        editor.handle_input("\"", window, cx);
 7166    });
 7167    cx.assert_editor_state(
 7168        &r#"
 7169            let x = ""ˇ
 7170        "#
 7171        .unindent(),
 7172    );
 7173
 7174    // Reset
 7175    cx.set_state(
 7176        &r#"
 7177            let x = ˇ
 7178        "#
 7179        .unindent(),
 7180    );
 7181
 7182    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7183    cx.update_editor(|editor, window, cx| {
 7184        editor.handle_input("\"", window, cx);
 7185        editor.handle_input(" ", window, cx);
 7186        editor.move_left(&Default::default(), window, cx);
 7187        editor.handle_input("\\", window, cx);
 7188        editor.handle_input("\"", window, cx);
 7189    });
 7190    cx.assert_editor_state(
 7191        &r#"
 7192            let x = "\"ˇ "
 7193        "#
 7194        .unindent(),
 7195    );
 7196
 7197    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7198    // mark. Nothing is inserted.
 7199    cx.update_editor(|editor, window, cx| {
 7200        editor.move_right(&Default::default(), window, cx);
 7201        editor.handle_input("\"", window, cx);
 7202    });
 7203    cx.assert_editor_state(
 7204        &r#"
 7205            let x = "\" "ˇ
 7206        "#
 7207        .unindent(),
 7208    );
 7209}
 7210
 7211#[gpui::test]
 7212async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7213    init_test(cx, |_| {});
 7214
 7215    let language = Arc::new(Language::new(
 7216        LanguageConfig {
 7217            brackets: BracketPairConfig {
 7218                pairs: vec![
 7219                    BracketPair {
 7220                        start: "{".to_string(),
 7221                        end: "}".to_string(),
 7222                        close: true,
 7223                        surround: true,
 7224                        newline: true,
 7225                    },
 7226                    BracketPair {
 7227                        start: "/* ".to_string(),
 7228                        end: "*/".to_string(),
 7229                        close: true,
 7230                        surround: true,
 7231                        ..Default::default()
 7232                    },
 7233                ],
 7234                ..Default::default()
 7235            },
 7236            ..Default::default()
 7237        },
 7238        Some(tree_sitter_rust::LANGUAGE.into()),
 7239    ));
 7240
 7241    let text = r#"
 7242        a
 7243        b
 7244        c
 7245    "#
 7246    .unindent();
 7247
 7248    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7249    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7250    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7251    editor
 7252        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7253        .await;
 7254
 7255    editor.update_in(cx, |editor, window, cx| {
 7256        editor.change_selections(None, window, cx, |s| {
 7257            s.select_display_ranges([
 7258                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7259                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7260                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7261            ])
 7262        });
 7263
 7264        editor.handle_input("{", window, cx);
 7265        editor.handle_input("{", window, cx);
 7266        editor.handle_input("{", window, cx);
 7267        assert_eq!(
 7268            editor.text(cx),
 7269            "
 7270                {{{a}}}
 7271                {{{b}}}
 7272                {{{c}}}
 7273            "
 7274            .unindent()
 7275        );
 7276        assert_eq!(
 7277            editor.selections.display_ranges(cx),
 7278            [
 7279                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7280                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7281                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7282            ]
 7283        );
 7284
 7285        editor.undo(&Undo, window, cx);
 7286        editor.undo(&Undo, window, cx);
 7287        editor.undo(&Undo, window, cx);
 7288        assert_eq!(
 7289            editor.text(cx),
 7290            "
 7291                a
 7292                b
 7293                c
 7294            "
 7295            .unindent()
 7296        );
 7297        assert_eq!(
 7298            editor.selections.display_ranges(cx),
 7299            [
 7300                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7301                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7302                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7303            ]
 7304        );
 7305
 7306        // Ensure inserting the first character of a multi-byte bracket pair
 7307        // doesn't surround the selections with the bracket.
 7308        editor.handle_input("/", window, cx);
 7309        assert_eq!(
 7310            editor.text(cx),
 7311            "
 7312                /
 7313                /
 7314                /
 7315            "
 7316            .unindent()
 7317        );
 7318        assert_eq!(
 7319            editor.selections.display_ranges(cx),
 7320            [
 7321                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7322                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7323                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7324            ]
 7325        );
 7326
 7327        editor.undo(&Undo, window, cx);
 7328        assert_eq!(
 7329            editor.text(cx),
 7330            "
 7331                a
 7332                b
 7333                c
 7334            "
 7335            .unindent()
 7336        );
 7337        assert_eq!(
 7338            editor.selections.display_ranges(cx),
 7339            [
 7340                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7341                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7342                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7343            ]
 7344        );
 7345
 7346        // Ensure inserting the last character of a multi-byte bracket pair
 7347        // doesn't surround the selections with the bracket.
 7348        editor.handle_input("*", window, cx);
 7349        assert_eq!(
 7350            editor.text(cx),
 7351            "
 7352                *
 7353                *
 7354                *
 7355            "
 7356            .unindent()
 7357        );
 7358        assert_eq!(
 7359            editor.selections.display_ranges(cx),
 7360            [
 7361                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7362                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7363                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7364            ]
 7365        );
 7366    });
 7367}
 7368
 7369#[gpui::test]
 7370async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7371    init_test(cx, |_| {});
 7372
 7373    let language = Arc::new(Language::new(
 7374        LanguageConfig {
 7375            brackets: BracketPairConfig {
 7376                pairs: vec![BracketPair {
 7377                    start: "{".to_string(),
 7378                    end: "}".to_string(),
 7379                    close: true,
 7380                    surround: true,
 7381                    newline: true,
 7382                }],
 7383                ..Default::default()
 7384            },
 7385            autoclose_before: "}".to_string(),
 7386            ..Default::default()
 7387        },
 7388        Some(tree_sitter_rust::LANGUAGE.into()),
 7389    ));
 7390
 7391    let text = r#"
 7392        a
 7393        b
 7394        c
 7395    "#
 7396    .unindent();
 7397
 7398    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7399    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7400    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7401    editor
 7402        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7403        .await;
 7404
 7405    editor.update_in(cx, |editor, window, cx| {
 7406        editor.change_selections(None, window, cx, |s| {
 7407            s.select_ranges([
 7408                Point::new(0, 1)..Point::new(0, 1),
 7409                Point::new(1, 1)..Point::new(1, 1),
 7410                Point::new(2, 1)..Point::new(2, 1),
 7411            ])
 7412        });
 7413
 7414        editor.handle_input("{", window, cx);
 7415        editor.handle_input("{", window, cx);
 7416        editor.handle_input("_", window, cx);
 7417        assert_eq!(
 7418            editor.text(cx),
 7419            "
 7420                a{{_}}
 7421                b{{_}}
 7422                c{{_}}
 7423            "
 7424            .unindent()
 7425        );
 7426        assert_eq!(
 7427            editor.selections.ranges::<Point>(cx),
 7428            [
 7429                Point::new(0, 4)..Point::new(0, 4),
 7430                Point::new(1, 4)..Point::new(1, 4),
 7431                Point::new(2, 4)..Point::new(2, 4)
 7432            ]
 7433        );
 7434
 7435        editor.backspace(&Default::default(), window, cx);
 7436        editor.backspace(&Default::default(), window, cx);
 7437        assert_eq!(
 7438            editor.text(cx),
 7439            "
 7440                a{}
 7441                b{}
 7442                c{}
 7443            "
 7444            .unindent()
 7445        );
 7446        assert_eq!(
 7447            editor.selections.ranges::<Point>(cx),
 7448            [
 7449                Point::new(0, 2)..Point::new(0, 2),
 7450                Point::new(1, 2)..Point::new(1, 2),
 7451                Point::new(2, 2)..Point::new(2, 2)
 7452            ]
 7453        );
 7454
 7455        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 7456        assert_eq!(
 7457            editor.text(cx),
 7458            "
 7459                a
 7460                b
 7461                c
 7462            "
 7463            .unindent()
 7464        );
 7465        assert_eq!(
 7466            editor.selections.ranges::<Point>(cx),
 7467            [
 7468                Point::new(0, 1)..Point::new(0, 1),
 7469                Point::new(1, 1)..Point::new(1, 1),
 7470                Point::new(2, 1)..Point::new(2, 1)
 7471            ]
 7472        );
 7473    });
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 7478    init_test(cx, |settings| {
 7479        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7480    });
 7481
 7482    let mut cx = EditorTestContext::new(cx).await;
 7483
 7484    let language = Arc::new(Language::new(
 7485        LanguageConfig {
 7486            brackets: BracketPairConfig {
 7487                pairs: vec![
 7488                    BracketPair {
 7489                        start: "{".to_string(),
 7490                        end: "}".to_string(),
 7491                        close: true,
 7492                        surround: true,
 7493                        newline: true,
 7494                    },
 7495                    BracketPair {
 7496                        start: "(".to_string(),
 7497                        end: ")".to_string(),
 7498                        close: true,
 7499                        surround: true,
 7500                        newline: true,
 7501                    },
 7502                    BracketPair {
 7503                        start: "[".to_string(),
 7504                        end: "]".to_string(),
 7505                        close: false,
 7506                        surround: true,
 7507                        newline: true,
 7508                    },
 7509                ],
 7510                ..Default::default()
 7511            },
 7512            autoclose_before: "})]".to_string(),
 7513            ..Default::default()
 7514        },
 7515        Some(tree_sitter_rust::LANGUAGE.into()),
 7516    ));
 7517
 7518    cx.language_registry().add(language.clone());
 7519    cx.update_buffer(|buffer, cx| {
 7520        buffer.set_language(Some(language), cx);
 7521    });
 7522
 7523    cx.set_state(
 7524        &"
 7525            {(ˇ)}
 7526            [[ˇ]]
 7527            {(ˇ)}
 7528        "
 7529        .unindent(),
 7530    );
 7531
 7532    cx.update_editor(|editor, window, cx| {
 7533        editor.backspace(&Default::default(), window, cx);
 7534        editor.backspace(&Default::default(), window, cx);
 7535    });
 7536
 7537    cx.assert_editor_state(
 7538        &"
 7539            ˇ
 7540            ˇ]]
 7541            ˇ
 7542        "
 7543        .unindent(),
 7544    );
 7545
 7546    cx.update_editor(|editor, window, cx| {
 7547        editor.handle_input("{", window, cx);
 7548        editor.handle_input("{", window, cx);
 7549        editor.move_right(&MoveRight, window, cx);
 7550        editor.move_right(&MoveRight, window, cx);
 7551        editor.move_left(&MoveLeft, window, cx);
 7552        editor.move_left(&MoveLeft, window, cx);
 7553        editor.backspace(&Default::default(), window, cx);
 7554    });
 7555
 7556    cx.assert_editor_state(
 7557        &"
 7558            {ˇ}
 7559            {ˇ}]]
 7560            {ˇ}
 7561        "
 7562        .unindent(),
 7563    );
 7564
 7565    cx.update_editor(|editor, window, cx| {
 7566        editor.backspace(&Default::default(), window, cx);
 7567    });
 7568
 7569    cx.assert_editor_state(
 7570        &"
 7571            ˇ
 7572            ˇ]]
 7573            ˇ
 7574        "
 7575        .unindent(),
 7576    );
 7577}
 7578
 7579#[gpui::test]
 7580async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 7581    init_test(cx, |_| {});
 7582
 7583    let language = Arc::new(Language::new(
 7584        LanguageConfig::default(),
 7585        Some(tree_sitter_rust::LANGUAGE.into()),
 7586    ));
 7587
 7588    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 7589    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7590    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7591    editor
 7592        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7593        .await;
 7594
 7595    editor.update_in(cx, |editor, window, cx| {
 7596        editor.set_auto_replace_emoji_shortcode(true);
 7597
 7598        editor.handle_input("Hello ", window, cx);
 7599        editor.handle_input(":wave", window, cx);
 7600        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 7601
 7602        editor.handle_input(":", window, cx);
 7603        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 7604
 7605        editor.handle_input(" :smile", window, cx);
 7606        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 7607
 7608        editor.handle_input(":", window, cx);
 7609        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 7610
 7611        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 7612        editor.handle_input(":wave", window, cx);
 7613        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 7614
 7615        editor.handle_input(":", window, cx);
 7616        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 7617
 7618        editor.handle_input(":1", window, cx);
 7619        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 7620
 7621        editor.handle_input(":", window, cx);
 7622        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 7623
 7624        // Ensure shortcode does not get replaced when it is part of a word
 7625        editor.handle_input(" Test:wave", window, cx);
 7626        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 7627
 7628        editor.handle_input(":", window, cx);
 7629        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 7630
 7631        editor.set_auto_replace_emoji_shortcode(false);
 7632
 7633        // Ensure shortcode does not get replaced when auto replace is off
 7634        editor.handle_input(" :wave", window, cx);
 7635        assert_eq!(
 7636            editor.text(cx),
 7637            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 7638        );
 7639
 7640        editor.handle_input(":", window, cx);
 7641        assert_eq!(
 7642            editor.text(cx),
 7643            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 7644        );
 7645    });
 7646}
 7647
 7648#[gpui::test]
 7649async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 7650    init_test(cx, |_| {});
 7651
 7652    let (text, insertion_ranges) = marked_text_ranges(
 7653        indoc! {"
 7654            ˇ
 7655        "},
 7656        false,
 7657    );
 7658
 7659    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7660    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7661
 7662    _ = editor.update_in(cx, |editor, window, cx| {
 7663        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 7664
 7665        editor
 7666            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7667            .unwrap();
 7668
 7669        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7670            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7671            assert_eq!(editor.text(cx), expected_text);
 7672            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7673        }
 7674
 7675        assert(
 7676            editor,
 7677            cx,
 7678            indoc! {"
 7679            type «» =•
 7680            "},
 7681        );
 7682
 7683        assert!(editor.context_menu_visible(), "There should be a matches");
 7684    });
 7685}
 7686
 7687#[gpui::test]
 7688async fn test_snippets(cx: &mut TestAppContext) {
 7689    init_test(cx, |_| {});
 7690
 7691    let (text, insertion_ranges) = marked_text_ranges(
 7692        indoc! {"
 7693            a.ˇ b
 7694            a.ˇ b
 7695            a.ˇ b
 7696        "},
 7697        false,
 7698    );
 7699
 7700    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7701    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7702
 7703    editor.update_in(cx, |editor, window, cx| {
 7704        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 7705
 7706        editor
 7707            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7708            .unwrap();
 7709
 7710        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7711            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7712            assert_eq!(editor.text(cx), expected_text);
 7713            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7714        }
 7715
 7716        assert(
 7717            editor,
 7718            cx,
 7719            indoc! {"
 7720                a.f(«one», two, «three») b
 7721                a.f(«one», two, «three») b
 7722                a.f(«one», two, «three») b
 7723            "},
 7724        );
 7725
 7726        // Can't move earlier than the first tab stop
 7727        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 7728        assert(
 7729            editor,
 7730            cx,
 7731            indoc! {"
 7732                a.f(«one», two, «three») b
 7733                a.f(«one», two, «three») b
 7734                a.f(«one», two, «three») b
 7735            "},
 7736        );
 7737
 7738        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7739        assert(
 7740            editor,
 7741            cx,
 7742            indoc! {"
 7743                a.f(one, «two», three) b
 7744                a.f(one, «two», three) b
 7745                a.f(one, «two», three) b
 7746            "},
 7747        );
 7748
 7749        editor.move_to_prev_snippet_tabstop(window, cx);
 7750        assert(
 7751            editor,
 7752            cx,
 7753            indoc! {"
 7754                a.f(«one», two, «three») b
 7755                a.f(«one», two, «three») b
 7756                a.f(«one», two, «three») b
 7757            "},
 7758        );
 7759
 7760        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7761        assert(
 7762            editor,
 7763            cx,
 7764            indoc! {"
 7765                a.f(one, «two», three) b
 7766                a.f(one, «two», three) b
 7767                a.f(one, «two», three) b
 7768            "},
 7769        );
 7770        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7771        assert(
 7772            editor,
 7773            cx,
 7774            indoc! {"
 7775                a.f(one, two, three)ˇ b
 7776                a.f(one, two, three)ˇ b
 7777                a.f(one, two, three)ˇ b
 7778            "},
 7779        );
 7780
 7781        // As soon as the last tab stop is reached, snippet state is gone
 7782        editor.move_to_prev_snippet_tabstop(window, cx);
 7783        assert(
 7784            editor,
 7785            cx,
 7786            indoc! {"
 7787                a.f(one, two, three)ˇ b
 7788                a.f(one, two, three)ˇ b
 7789                a.f(one, two, three)ˇ b
 7790            "},
 7791        );
 7792    });
 7793}
 7794
 7795#[gpui::test]
 7796async fn test_document_format_during_save(cx: &mut TestAppContext) {
 7797    init_test(cx, |_| {});
 7798
 7799    let fs = FakeFs::new(cx.executor());
 7800    fs.insert_file(path!("/file.rs"), Default::default()).await;
 7801
 7802    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 7803
 7804    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7805    language_registry.add(rust_lang());
 7806    let mut fake_servers = language_registry.register_fake_lsp(
 7807        "Rust",
 7808        FakeLspAdapter {
 7809            capabilities: lsp::ServerCapabilities {
 7810                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7811                ..Default::default()
 7812            },
 7813            ..Default::default()
 7814        },
 7815    );
 7816
 7817    let buffer = project
 7818        .update(cx, |project, cx| {
 7819            project.open_local_buffer(path!("/file.rs"), cx)
 7820        })
 7821        .await
 7822        .unwrap();
 7823
 7824    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7825    let (editor, cx) = cx.add_window_view(|window, cx| {
 7826        build_editor_with_project(project.clone(), buffer, window, cx)
 7827    });
 7828    editor.update_in(cx, |editor, window, cx| {
 7829        editor.set_text("one\ntwo\nthree\n", window, cx)
 7830    });
 7831    assert!(cx.read(|cx| editor.is_dirty(cx)));
 7832
 7833    cx.executor().start_waiting();
 7834    let fake_server = fake_servers.next().await.unwrap();
 7835
 7836    {
 7837        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 7838            move |params, _| async move {
 7839                assert_eq!(
 7840                    params.text_document.uri,
 7841                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7842                );
 7843                assert_eq!(params.options.tab_size, 4);
 7844                Ok(Some(vec![lsp::TextEdit::new(
 7845                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 7846                    ", ".to_string(),
 7847                )]))
 7848            },
 7849        );
 7850        let save = editor
 7851            .update_in(cx, |editor, window, cx| {
 7852                editor.save(true, project.clone(), window, cx)
 7853            })
 7854            .unwrap();
 7855        cx.executor().start_waiting();
 7856        save.await;
 7857
 7858        assert_eq!(
 7859            editor.update(cx, |editor, cx| editor.text(cx)),
 7860            "one, two\nthree\n"
 7861        );
 7862        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7863    }
 7864
 7865    {
 7866        editor.update_in(cx, |editor, window, cx| {
 7867            editor.set_text("one\ntwo\nthree\n", window, cx)
 7868        });
 7869        assert!(cx.read(|cx| editor.is_dirty(cx)));
 7870
 7871        // Ensure we can still save even if formatting hangs.
 7872        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 7873            move |params, _| async move {
 7874                assert_eq!(
 7875                    params.text_document.uri,
 7876                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7877                );
 7878                futures::future::pending::<()>().await;
 7879                unreachable!()
 7880            },
 7881        );
 7882        let save = editor
 7883            .update_in(cx, |editor, window, cx| {
 7884                editor.save(true, project.clone(), window, cx)
 7885            })
 7886            .unwrap();
 7887        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 7888        cx.executor().start_waiting();
 7889        save.await;
 7890        assert_eq!(
 7891            editor.update(cx, |editor, cx| editor.text(cx)),
 7892            "one\ntwo\nthree\n"
 7893        );
 7894    }
 7895
 7896    // For non-dirty buffer, no formatting request should be sent
 7897    {
 7898        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7899
 7900        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7901            panic!("Should not be invoked on non-dirty buffer");
 7902        });
 7903        let save = editor
 7904            .update_in(cx, |editor, window, cx| {
 7905                editor.save(true, project.clone(), window, cx)
 7906            })
 7907            .unwrap();
 7908        cx.executor().start_waiting();
 7909        save.await;
 7910    }
 7911
 7912    // Set rust language override and assert overridden tabsize is sent to language server
 7913    update_test_language_settings(cx, |settings| {
 7914        settings.languages.insert(
 7915            "Rust".into(),
 7916            LanguageSettingsContent {
 7917                tab_size: NonZeroU32::new(8),
 7918                ..Default::default()
 7919            },
 7920        );
 7921    });
 7922
 7923    {
 7924        editor.update_in(cx, |editor, window, cx| {
 7925            editor.set_text("somehting_new\n", window, cx)
 7926        });
 7927        assert!(cx.read(|cx| editor.is_dirty(cx)));
 7928        let _formatting_request_signal = fake_server
 7929            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7930                assert_eq!(
 7931                    params.text_document.uri,
 7932                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7933                );
 7934                assert_eq!(params.options.tab_size, 8);
 7935                Ok(Some(vec![]))
 7936            });
 7937        let save = editor
 7938            .update_in(cx, |editor, window, cx| {
 7939                editor.save(true, project.clone(), window, cx)
 7940            })
 7941            .unwrap();
 7942        cx.executor().start_waiting();
 7943        save.await;
 7944    }
 7945}
 7946
 7947#[gpui::test]
 7948async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 7949    init_test(cx, |_| {});
 7950
 7951    let cols = 4;
 7952    let rows = 10;
 7953    let sample_text_1 = sample_text(rows, cols, 'a');
 7954    assert_eq!(
 7955        sample_text_1,
 7956        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 7957    );
 7958    let sample_text_2 = sample_text(rows, cols, 'l');
 7959    assert_eq!(
 7960        sample_text_2,
 7961        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 7962    );
 7963    let sample_text_3 = sample_text(rows, cols, 'v');
 7964    assert_eq!(
 7965        sample_text_3,
 7966        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 7967    );
 7968
 7969    let fs = FakeFs::new(cx.executor());
 7970    fs.insert_tree(
 7971        path!("/a"),
 7972        json!({
 7973            "main.rs": sample_text_1,
 7974            "other.rs": sample_text_2,
 7975            "lib.rs": sample_text_3,
 7976        }),
 7977    )
 7978    .await;
 7979
 7980    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 7981    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7982    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 7983
 7984    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7985    language_registry.add(rust_lang());
 7986    let mut fake_servers = language_registry.register_fake_lsp(
 7987        "Rust",
 7988        FakeLspAdapter {
 7989            capabilities: lsp::ServerCapabilities {
 7990                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7991                ..Default::default()
 7992            },
 7993            ..Default::default()
 7994        },
 7995    );
 7996
 7997    let worktree = project.update(cx, |project, cx| {
 7998        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 7999        assert_eq!(worktrees.len(), 1);
 8000        worktrees.pop().unwrap()
 8001    });
 8002    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8003
 8004    let buffer_1 = project
 8005        .update(cx, |project, cx| {
 8006            project.open_buffer((worktree_id, "main.rs"), cx)
 8007        })
 8008        .await
 8009        .unwrap();
 8010    let buffer_2 = project
 8011        .update(cx, |project, cx| {
 8012            project.open_buffer((worktree_id, "other.rs"), cx)
 8013        })
 8014        .await
 8015        .unwrap();
 8016    let buffer_3 = project
 8017        .update(cx, |project, cx| {
 8018            project.open_buffer((worktree_id, "lib.rs"), cx)
 8019        })
 8020        .await
 8021        .unwrap();
 8022
 8023    let multi_buffer = cx.new(|cx| {
 8024        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8025        multi_buffer.push_excerpts(
 8026            buffer_1.clone(),
 8027            [
 8028                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8029                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8030                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8031            ],
 8032            cx,
 8033        );
 8034        multi_buffer.push_excerpts(
 8035            buffer_2.clone(),
 8036            [
 8037                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8038                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8039                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8040            ],
 8041            cx,
 8042        );
 8043        multi_buffer.push_excerpts(
 8044            buffer_3.clone(),
 8045            [
 8046                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8047                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8048                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8049            ],
 8050            cx,
 8051        );
 8052        multi_buffer
 8053    });
 8054    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8055        Editor::new(
 8056            EditorMode::Full,
 8057            multi_buffer,
 8058            Some(project.clone()),
 8059            window,
 8060            cx,
 8061        )
 8062    });
 8063
 8064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8065        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8066            s.select_ranges(Some(1..2))
 8067        });
 8068        editor.insert("|one|two|three|", window, cx);
 8069    });
 8070    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8071    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8072        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8073            s.select_ranges(Some(60..70))
 8074        });
 8075        editor.insert("|four|five|six|", window, cx);
 8076    });
 8077    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8078
 8079    // First two buffers should be edited, but not the third one.
 8080    assert_eq!(
 8081        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8082        "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}",
 8083    );
 8084    buffer_1.update(cx, |buffer, _| {
 8085        assert!(buffer.is_dirty());
 8086        assert_eq!(
 8087            buffer.text(),
 8088            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8089        )
 8090    });
 8091    buffer_2.update(cx, |buffer, _| {
 8092        assert!(buffer.is_dirty());
 8093        assert_eq!(
 8094            buffer.text(),
 8095            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8096        )
 8097    });
 8098    buffer_3.update(cx, |buffer, _| {
 8099        assert!(!buffer.is_dirty());
 8100        assert_eq!(buffer.text(), sample_text_3,)
 8101    });
 8102    cx.executor().run_until_parked();
 8103
 8104    cx.executor().start_waiting();
 8105    let save = multi_buffer_editor
 8106        .update_in(cx, |editor, window, cx| {
 8107            editor.save(true, project.clone(), window, cx)
 8108        })
 8109        .unwrap();
 8110
 8111    let fake_server = fake_servers.next().await.unwrap();
 8112    fake_server
 8113        .server
 8114        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8115            Ok(Some(vec![lsp::TextEdit::new(
 8116                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8117                format!("[{} formatted]", params.text_document.uri),
 8118            )]))
 8119        })
 8120        .detach();
 8121    save.await;
 8122
 8123    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8124    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8125    assert_eq!(
 8126        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8127        uri!(
 8128            "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}"
 8129        ),
 8130    );
 8131    buffer_1.update(cx, |buffer, _| {
 8132        assert!(!buffer.is_dirty());
 8133        assert_eq!(
 8134            buffer.text(),
 8135            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8136        )
 8137    });
 8138    buffer_2.update(cx, |buffer, _| {
 8139        assert!(!buffer.is_dirty());
 8140        assert_eq!(
 8141            buffer.text(),
 8142            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8143        )
 8144    });
 8145    buffer_3.update(cx, |buffer, _| {
 8146        assert!(!buffer.is_dirty());
 8147        assert_eq!(buffer.text(), sample_text_3,)
 8148    });
 8149}
 8150
 8151#[gpui::test]
 8152async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8153    init_test(cx, |_| {});
 8154
 8155    let fs = FakeFs::new(cx.executor());
 8156    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8157
 8158    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8159
 8160    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8161    language_registry.add(rust_lang());
 8162    let mut fake_servers = language_registry.register_fake_lsp(
 8163        "Rust",
 8164        FakeLspAdapter {
 8165            capabilities: lsp::ServerCapabilities {
 8166                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8167                ..Default::default()
 8168            },
 8169            ..Default::default()
 8170        },
 8171    );
 8172
 8173    let buffer = project
 8174        .update(cx, |project, cx| {
 8175            project.open_local_buffer(path!("/file.rs"), cx)
 8176        })
 8177        .await
 8178        .unwrap();
 8179
 8180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8181    let (editor, cx) = cx.add_window_view(|window, cx| {
 8182        build_editor_with_project(project.clone(), buffer, window, cx)
 8183    });
 8184    editor.update_in(cx, |editor, window, cx| {
 8185        editor.set_text("one\ntwo\nthree\n", window, cx)
 8186    });
 8187    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8188
 8189    cx.executor().start_waiting();
 8190    let fake_server = fake_servers.next().await.unwrap();
 8191
 8192    let save = editor
 8193        .update_in(cx, |editor, window, cx| {
 8194            editor.save(true, project.clone(), window, cx)
 8195        })
 8196        .unwrap();
 8197    fake_server
 8198        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8199            assert_eq!(
 8200                params.text_document.uri,
 8201                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8202            );
 8203            assert_eq!(params.options.tab_size, 4);
 8204            Ok(Some(vec![lsp::TextEdit::new(
 8205                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8206                ", ".to_string(),
 8207            )]))
 8208        })
 8209        .next()
 8210        .await;
 8211    cx.executor().start_waiting();
 8212    save.await;
 8213    assert_eq!(
 8214        editor.update(cx, |editor, cx| editor.text(cx)),
 8215        "one, two\nthree\n"
 8216    );
 8217    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8218
 8219    editor.update_in(cx, |editor, window, cx| {
 8220        editor.set_text("one\ntwo\nthree\n", window, cx)
 8221    });
 8222    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8223
 8224    // Ensure we can still save even if formatting hangs.
 8225    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8226        move |params, _| async move {
 8227            assert_eq!(
 8228                params.text_document.uri,
 8229                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8230            );
 8231            futures::future::pending::<()>().await;
 8232            unreachable!()
 8233        },
 8234    );
 8235    let save = editor
 8236        .update_in(cx, |editor, window, cx| {
 8237            editor.save(true, project.clone(), window, cx)
 8238        })
 8239        .unwrap();
 8240    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8241    cx.executor().start_waiting();
 8242    save.await;
 8243    assert_eq!(
 8244        editor.update(cx, |editor, cx| editor.text(cx)),
 8245        "one\ntwo\nthree\n"
 8246    );
 8247    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8248
 8249    // For non-dirty buffer, no formatting request should be sent
 8250    let save = editor
 8251        .update_in(cx, |editor, window, cx| {
 8252            editor.save(true, project.clone(), window, cx)
 8253        })
 8254        .unwrap();
 8255    let _pending_format_request = fake_server
 8256        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8257            panic!("Should not be invoked on non-dirty buffer");
 8258        })
 8259        .next();
 8260    cx.executor().start_waiting();
 8261    save.await;
 8262
 8263    // Set Rust language override and assert overridden tabsize is sent to language server
 8264    update_test_language_settings(cx, |settings| {
 8265        settings.languages.insert(
 8266            "Rust".into(),
 8267            LanguageSettingsContent {
 8268                tab_size: NonZeroU32::new(8),
 8269                ..Default::default()
 8270            },
 8271        );
 8272    });
 8273
 8274    editor.update_in(cx, |editor, window, cx| {
 8275        editor.set_text("somehting_new\n", window, cx)
 8276    });
 8277    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8278    let save = editor
 8279        .update_in(cx, |editor, window, cx| {
 8280            editor.save(true, project.clone(), window, cx)
 8281        })
 8282        .unwrap();
 8283    fake_server
 8284        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8285            assert_eq!(
 8286                params.text_document.uri,
 8287                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8288            );
 8289            assert_eq!(params.options.tab_size, 8);
 8290            Ok(Some(vec![]))
 8291        })
 8292        .next()
 8293        .await;
 8294    cx.executor().start_waiting();
 8295    save.await;
 8296}
 8297
 8298#[gpui::test]
 8299async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8300    init_test(cx, |settings| {
 8301        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8302            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8303        ))
 8304    });
 8305
 8306    let fs = FakeFs::new(cx.executor());
 8307    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8308
 8309    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8310
 8311    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8312    language_registry.add(Arc::new(Language::new(
 8313        LanguageConfig {
 8314            name: "Rust".into(),
 8315            matcher: LanguageMatcher {
 8316                path_suffixes: vec!["rs".to_string()],
 8317                ..Default::default()
 8318            },
 8319            ..LanguageConfig::default()
 8320        },
 8321        Some(tree_sitter_rust::LANGUAGE.into()),
 8322    )));
 8323    update_test_language_settings(cx, |settings| {
 8324        // Enable Prettier formatting for the same buffer, and ensure
 8325        // LSP is called instead of Prettier.
 8326        settings.defaults.prettier = Some(PrettierSettings {
 8327            allowed: true,
 8328            ..PrettierSettings::default()
 8329        });
 8330    });
 8331    let mut fake_servers = language_registry.register_fake_lsp(
 8332        "Rust",
 8333        FakeLspAdapter {
 8334            capabilities: lsp::ServerCapabilities {
 8335                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8336                ..Default::default()
 8337            },
 8338            ..Default::default()
 8339        },
 8340    );
 8341
 8342    let buffer = project
 8343        .update(cx, |project, cx| {
 8344            project.open_local_buffer(path!("/file.rs"), cx)
 8345        })
 8346        .await
 8347        .unwrap();
 8348
 8349    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8350    let (editor, cx) = cx.add_window_view(|window, cx| {
 8351        build_editor_with_project(project.clone(), buffer, window, cx)
 8352    });
 8353    editor.update_in(cx, |editor, window, cx| {
 8354        editor.set_text("one\ntwo\nthree\n", window, cx)
 8355    });
 8356
 8357    cx.executor().start_waiting();
 8358    let fake_server = fake_servers.next().await.unwrap();
 8359
 8360    let format = editor
 8361        .update_in(cx, |editor, window, cx| {
 8362            editor.perform_format(
 8363                project.clone(),
 8364                FormatTrigger::Manual,
 8365                FormatTarget::Buffers,
 8366                window,
 8367                cx,
 8368            )
 8369        })
 8370        .unwrap();
 8371    fake_server
 8372        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8373            assert_eq!(
 8374                params.text_document.uri,
 8375                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8376            );
 8377            assert_eq!(params.options.tab_size, 4);
 8378            Ok(Some(vec![lsp::TextEdit::new(
 8379                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8380                ", ".to_string(),
 8381            )]))
 8382        })
 8383        .next()
 8384        .await;
 8385    cx.executor().start_waiting();
 8386    format.await;
 8387    assert_eq!(
 8388        editor.update(cx, |editor, cx| editor.text(cx)),
 8389        "one, two\nthree\n"
 8390    );
 8391
 8392    editor.update_in(cx, |editor, window, cx| {
 8393        editor.set_text("one\ntwo\nthree\n", window, cx)
 8394    });
 8395    // Ensure we don't lock if formatting hangs.
 8396    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8397        move |params, _| async move {
 8398            assert_eq!(
 8399                params.text_document.uri,
 8400                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8401            );
 8402            futures::future::pending::<()>().await;
 8403            unreachable!()
 8404        },
 8405    );
 8406    let format = editor
 8407        .update_in(cx, |editor, window, cx| {
 8408            editor.perform_format(
 8409                project,
 8410                FormatTrigger::Manual,
 8411                FormatTarget::Buffers,
 8412                window,
 8413                cx,
 8414            )
 8415        })
 8416        .unwrap();
 8417    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8418    cx.executor().start_waiting();
 8419    format.await;
 8420    assert_eq!(
 8421        editor.update(cx, |editor, cx| editor.text(cx)),
 8422        "one\ntwo\nthree\n"
 8423    );
 8424}
 8425
 8426#[gpui::test]
 8427async fn test_multiple_formatters(cx: &mut TestAppContext) {
 8428    init_test(cx, |settings| {
 8429        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 8430        settings.defaults.formatter =
 8431            Some(language_settings::SelectedFormatter::List(FormatterList(
 8432                vec![
 8433                    Formatter::LanguageServer { name: None },
 8434                    Formatter::CodeActions(
 8435                        [
 8436                            ("code-action-1".into(), true),
 8437                            ("code-action-2".into(), true),
 8438                        ]
 8439                        .into_iter()
 8440                        .collect(),
 8441                    ),
 8442                ]
 8443                .into(),
 8444            )))
 8445    });
 8446
 8447    let fs = FakeFs::new(cx.executor());
 8448    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 8449        .await;
 8450
 8451    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8452    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8453    language_registry.add(rust_lang());
 8454
 8455    let mut fake_servers = language_registry.register_fake_lsp(
 8456        "Rust",
 8457        FakeLspAdapter {
 8458            capabilities: lsp::ServerCapabilities {
 8459                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8460                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 8461                    commands: vec!["the-command-for-code-action-1".into()],
 8462                    ..Default::default()
 8463                }),
 8464                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8465                ..Default::default()
 8466            },
 8467            ..Default::default()
 8468        },
 8469    );
 8470
 8471    let buffer = project
 8472        .update(cx, |project, cx| {
 8473            project.open_local_buffer(path!("/file.rs"), cx)
 8474        })
 8475        .await
 8476        .unwrap();
 8477
 8478    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8479    let (editor, cx) = cx.add_window_view(|window, cx| {
 8480        build_editor_with_project(project.clone(), buffer, window, cx)
 8481    });
 8482
 8483    cx.executor().start_waiting();
 8484
 8485    let fake_server = fake_servers.next().await.unwrap();
 8486    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8487        move |_params, _| async move {
 8488            Ok(Some(vec![lsp::TextEdit::new(
 8489                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8490                "applied-formatting\n".to_string(),
 8491            )]))
 8492        },
 8493    );
 8494    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8495        move |params, _| async move {
 8496            assert_eq!(
 8497                params.context.only,
 8498                Some(vec!["code-action-1".into(), "code-action-2".into()])
 8499            );
 8500            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 8501            Ok(Some(vec![
 8502                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8503                    kind: Some("code-action-1".into()),
 8504                    edit: Some(lsp::WorkspaceEdit::new(
 8505                        [(
 8506                            uri.clone(),
 8507                            vec![lsp::TextEdit::new(
 8508                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8509                                "applied-code-action-1-edit\n".to_string(),
 8510                            )],
 8511                        )]
 8512                        .into_iter()
 8513                        .collect(),
 8514                    )),
 8515                    command: Some(lsp::Command {
 8516                        command: "the-command-for-code-action-1".into(),
 8517                        ..Default::default()
 8518                    }),
 8519                    ..Default::default()
 8520                }),
 8521                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 8522                    kind: Some("code-action-2".into()),
 8523                    edit: Some(lsp::WorkspaceEdit::new(
 8524                        [(
 8525                            uri.clone(),
 8526                            vec![lsp::TextEdit::new(
 8527                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8528                                "applied-code-action-2-edit\n".to_string(),
 8529                            )],
 8530                        )]
 8531                        .into_iter()
 8532                        .collect(),
 8533                    )),
 8534                    ..Default::default()
 8535                }),
 8536            ]))
 8537        },
 8538    );
 8539
 8540    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 8541        move |params, _| async move { Ok(params) }
 8542    });
 8543
 8544    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 8545    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 8546        let fake = fake_server.clone();
 8547        let lock = command_lock.clone();
 8548        move |params, _| {
 8549            assert_eq!(params.command, "the-command-for-code-action-1");
 8550            let fake = fake.clone();
 8551            let lock = lock.clone();
 8552            async move {
 8553                lock.lock().await;
 8554                fake.server
 8555                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 8556                        label: None,
 8557                        edit: lsp::WorkspaceEdit {
 8558                            changes: Some(
 8559                                [(
 8560                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 8561                                    vec![lsp::TextEdit {
 8562                                        range: lsp::Range::new(
 8563                                            lsp::Position::new(0, 0),
 8564                                            lsp::Position::new(0, 0),
 8565                                        ),
 8566                                        new_text: "applied-code-action-1-command\n".into(),
 8567                                    }],
 8568                                )]
 8569                                .into_iter()
 8570                                .collect(),
 8571                            ),
 8572                            ..Default::default()
 8573                        },
 8574                    })
 8575                    .await
 8576                    .unwrap();
 8577                Ok(Some(json!(null)))
 8578            }
 8579        }
 8580    });
 8581
 8582    cx.executor().start_waiting();
 8583    editor
 8584        .update_in(cx, |editor, window, cx| {
 8585            editor.perform_format(
 8586                project.clone(),
 8587                FormatTrigger::Manual,
 8588                FormatTarget::Buffers,
 8589                window,
 8590                cx,
 8591            )
 8592        })
 8593        .unwrap()
 8594        .await;
 8595    editor.update(cx, |editor, cx| {
 8596        assert_eq!(
 8597            editor.text(cx),
 8598            r#"
 8599                applied-code-action-2-edit
 8600                applied-code-action-1-command
 8601                applied-code-action-1-edit
 8602                applied-formatting
 8603                one
 8604                two
 8605                three
 8606            "#
 8607            .unindent()
 8608        );
 8609    });
 8610
 8611    editor.update_in(cx, |editor, window, cx| {
 8612        editor.undo(&Default::default(), window, cx);
 8613        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8614    });
 8615
 8616    // Perform a manual edit while waiting for an LSP command
 8617    // that's being run as part of a formatting code action.
 8618    let lock_guard = command_lock.lock().await;
 8619    let format = editor
 8620        .update_in(cx, |editor, window, cx| {
 8621            editor.perform_format(
 8622                project.clone(),
 8623                FormatTrigger::Manual,
 8624                FormatTarget::Buffers,
 8625                window,
 8626                cx,
 8627            )
 8628        })
 8629        .unwrap();
 8630    cx.run_until_parked();
 8631    editor.update(cx, |editor, cx| {
 8632        assert_eq!(
 8633            editor.text(cx),
 8634            r#"
 8635                applied-code-action-1-edit
 8636                applied-formatting
 8637                one
 8638                two
 8639                three
 8640            "#
 8641            .unindent()
 8642        );
 8643
 8644        editor.buffer.update(cx, |buffer, cx| {
 8645            let ix = buffer.len(cx);
 8646            buffer.edit([(ix..ix, "edited\n")], None, cx);
 8647        });
 8648    });
 8649
 8650    // Allow the LSP command to proceed. Because the buffer was edited,
 8651    // the second code action will not be run.
 8652    drop(lock_guard);
 8653    format.await;
 8654    editor.update_in(cx, |editor, window, cx| {
 8655        assert_eq!(
 8656            editor.text(cx),
 8657            r#"
 8658                applied-code-action-1-command
 8659                applied-code-action-1-edit
 8660                applied-formatting
 8661                one
 8662                two
 8663                three
 8664                edited
 8665            "#
 8666            .unindent()
 8667        );
 8668
 8669        // The manual edit is undone first, because it is the last thing the user did
 8670        // (even though the command completed afterwards).
 8671        editor.undo(&Default::default(), window, cx);
 8672        assert_eq!(
 8673            editor.text(cx),
 8674            r#"
 8675                applied-code-action-1-command
 8676                applied-code-action-1-edit
 8677                applied-formatting
 8678                one
 8679                two
 8680                three
 8681            "#
 8682            .unindent()
 8683        );
 8684
 8685        // All the formatting (including the command, which completed after the manual edit)
 8686        // is undone together.
 8687        editor.undo(&Default::default(), window, cx);
 8688        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 8689    });
 8690}
 8691
 8692#[gpui::test]
 8693async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 8694    init_test(cx, |settings| {
 8695        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8696            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8697        ))
 8698    });
 8699
 8700    let fs = FakeFs::new(cx.executor());
 8701    fs.insert_file(path!("/file.ts"), Default::default()).await;
 8702
 8703    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8704
 8705    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8706    language_registry.add(Arc::new(Language::new(
 8707        LanguageConfig {
 8708            name: "TypeScript".into(),
 8709            matcher: LanguageMatcher {
 8710                path_suffixes: vec!["ts".to_string()],
 8711                ..Default::default()
 8712            },
 8713            ..LanguageConfig::default()
 8714        },
 8715        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 8716    )));
 8717    update_test_language_settings(cx, |settings| {
 8718        settings.defaults.prettier = Some(PrettierSettings {
 8719            allowed: true,
 8720            ..PrettierSettings::default()
 8721        });
 8722    });
 8723    let mut fake_servers = language_registry.register_fake_lsp(
 8724        "TypeScript",
 8725        FakeLspAdapter {
 8726            capabilities: lsp::ServerCapabilities {
 8727                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8728                ..Default::default()
 8729            },
 8730            ..Default::default()
 8731        },
 8732    );
 8733
 8734    let buffer = project
 8735        .update(cx, |project, cx| {
 8736            project.open_local_buffer(path!("/file.ts"), cx)
 8737        })
 8738        .await
 8739        .unwrap();
 8740
 8741    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8742    let (editor, cx) = cx.add_window_view(|window, cx| {
 8743        build_editor_with_project(project.clone(), buffer, window, cx)
 8744    });
 8745    editor.update_in(cx, |editor, window, cx| {
 8746        editor.set_text(
 8747            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8748            window,
 8749            cx,
 8750        )
 8751    });
 8752
 8753    cx.executor().start_waiting();
 8754    let fake_server = fake_servers.next().await.unwrap();
 8755
 8756    let format = editor
 8757        .update_in(cx, |editor, window, cx| {
 8758            editor.perform_code_action_kind(
 8759                project.clone(),
 8760                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8761                window,
 8762                cx,
 8763            )
 8764        })
 8765        .unwrap();
 8766    fake_server
 8767        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 8768            assert_eq!(
 8769                params.text_document.uri,
 8770                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8771            );
 8772            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 8773                lsp::CodeAction {
 8774                    title: "Organize Imports".to_string(),
 8775                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 8776                    edit: Some(lsp::WorkspaceEdit {
 8777                        changes: Some(
 8778                            [(
 8779                                params.text_document.uri.clone(),
 8780                                vec![lsp::TextEdit::new(
 8781                                    lsp::Range::new(
 8782                                        lsp::Position::new(1, 0),
 8783                                        lsp::Position::new(2, 0),
 8784                                    ),
 8785                                    "".to_string(),
 8786                                )],
 8787                            )]
 8788                            .into_iter()
 8789                            .collect(),
 8790                        ),
 8791                        ..Default::default()
 8792                    }),
 8793                    ..Default::default()
 8794                },
 8795            )]))
 8796        })
 8797        .next()
 8798        .await;
 8799    cx.executor().start_waiting();
 8800    format.await;
 8801    assert_eq!(
 8802        editor.update(cx, |editor, cx| editor.text(cx)),
 8803        "import { a } from 'module';\n\nconst x = a;\n"
 8804    );
 8805
 8806    editor.update_in(cx, |editor, window, cx| {
 8807        editor.set_text(
 8808            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8809            window,
 8810            cx,
 8811        )
 8812    });
 8813    // Ensure we don't lock if code action hangs.
 8814    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 8815        move |params, _| async move {
 8816            assert_eq!(
 8817                params.text_document.uri,
 8818                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8819            );
 8820            futures::future::pending::<()>().await;
 8821            unreachable!()
 8822        },
 8823    );
 8824    let format = editor
 8825        .update_in(cx, |editor, window, cx| {
 8826            editor.perform_code_action_kind(
 8827                project,
 8828                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8829                window,
 8830                cx,
 8831            )
 8832        })
 8833        .unwrap();
 8834    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 8835    cx.executor().start_waiting();
 8836    format.await;
 8837    assert_eq!(
 8838        editor.update(cx, |editor, cx| editor.text(cx)),
 8839        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 8840    );
 8841}
 8842
 8843#[gpui::test]
 8844async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 8845    init_test(cx, |_| {});
 8846
 8847    let mut cx = EditorLspTestContext::new_rust(
 8848        lsp::ServerCapabilities {
 8849            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8850            ..Default::default()
 8851        },
 8852        cx,
 8853    )
 8854    .await;
 8855
 8856    cx.set_state(indoc! {"
 8857        one.twoˇ
 8858    "});
 8859
 8860    // The format request takes a long time. When it completes, it inserts
 8861    // a newline and an indent before the `.`
 8862    cx.lsp
 8863        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 8864            let executor = cx.background_executor().clone();
 8865            async move {
 8866                executor.timer(Duration::from_millis(100)).await;
 8867                Ok(Some(vec![lsp::TextEdit {
 8868                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 8869                    new_text: "\n    ".into(),
 8870                }]))
 8871            }
 8872        });
 8873
 8874    // Submit a format request.
 8875    let format_1 = cx
 8876        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8877        .unwrap();
 8878    cx.executor().run_until_parked();
 8879
 8880    // Submit a second format request.
 8881    let format_2 = cx
 8882        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8883        .unwrap();
 8884    cx.executor().run_until_parked();
 8885
 8886    // Wait for both format requests to complete
 8887    cx.executor().advance_clock(Duration::from_millis(200));
 8888    cx.executor().start_waiting();
 8889    format_1.await.unwrap();
 8890    cx.executor().start_waiting();
 8891    format_2.await.unwrap();
 8892
 8893    // The formatting edits only happens once.
 8894    cx.assert_editor_state(indoc! {"
 8895        one
 8896            .twoˇ
 8897    "});
 8898}
 8899
 8900#[gpui::test]
 8901async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 8902    init_test(cx, |settings| {
 8903        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 8904    });
 8905
 8906    let mut cx = EditorLspTestContext::new_rust(
 8907        lsp::ServerCapabilities {
 8908            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8909            ..Default::default()
 8910        },
 8911        cx,
 8912    )
 8913    .await;
 8914
 8915    // Set up a buffer white some trailing whitespace and no trailing newline.
 8916    cx.set_state(
 8917        &[
 8918            "one ",   //
 8919            "twoˇ",   //
 8920            "three ", //
 8921            "four",   //
 8922        ]
 8923        .join("\n"),
 8924    );
 8925
 8926    // Submit a format request.
 8927    let format = cx
 8928        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8929        .unwrap();
 8930
 8931    // Record which buffer changes have been sent to the language server
 8932    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 8933    cx.lsp
 8934        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 8935            let buffer_changes = buffer_changes.clone();
 8936            move |params, _| {
 8937                buffer_changes.lock().extend(
 8938                    params
 8939                        .content_changes
 8940                        .into_iter()
 8941                        .map(|e| (e.range.unwrap(), e.text)),
 8942                );
 8943            }
 8944        });
 8945
 8946    // Handle formatting requests to the language server.
 8947    cx.lsp
 8948        .set_request_handler::<lsp::request::Formatting, _, _>({
 8949            let buffer_changes = buffer_changes.clone();
 8950            move |_, _| {
 8951                // When formatting is requested, trailing whitespace has already been stripped,
 8952                // and the trailing newline has already been added.
 8953                assert_eq!(
 8954                    &buffer_changes.lock()[1..],
 8955                    &[
 8956                        (
 8957                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 8958                            "".into()
 8959                        ),
 8960                        (
 8961                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 8962                            "".into()
 8963                        ),
 8964                        (
 8965                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 8966                            "\n".into()
 8967                        ),
 8968                    ]
 8969                );
 8970
 8971                // Insert blank lines between each line of the buffer.
 8972                async move {
 8973                    Ok(Some(vec![
 8974                        lsp::TextEdit {
 8975                            range: lsp::Range::new(
 8976                                lsp::Position::new(1, 0),
 8977                                lsp::Position::new(1, 0),
 8978                            ),
 8979                            new_text: "\n".into(),
 8980                        },
 8981                        lsp::TextEdit {
 8982                            range: lsp::Range::new(
 8983                                lsp::Position::new(2, 0),
 8984                                lsp::Position::new(2, 0),
 8985                            ),
 8986                            new_text: "\n".into(),
 8987                        },
 8988                    ]))
 8989                }
 8990            }
 8991        });
 8992
 8993    // After formatting the buffer, the trailing whitespace is stripped,
 8994    // a newline is appended, and the edits provided by the language server
 8995    // have been applied.
 8996    format.await.unwrap();
 8997    cx.assert_editor_state(
 8998        &[
 8999            "one",   //
 9000            "",      //
 9001            "twoˇ",  //
 9002            "",      //
 9003            "three", //
 9004            "four",  //
 9005            "",      //
 9006        ]
 9007        .join("\n"),
 9008    );
 9009
 9010    // Undoing the formatting undoes the trailing whitespace removal, the
 9011    // trailing newline, and the LSP edits.
 9012    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9013    cx.assert_editor_state(
 9014        &[
 9015            "one ",   //
 9016            "twoˇ",   //
 9017            "three ", //
 9018            "four",   //
 9019        ]
 9020        .join("\n"),
 9021    );
 9022}
 9023
 9024#[gpui::test]
 9025async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9026    cx: &mut TestAppContext,
 9027) {
 9028    init_test(cx, |_| {});
 9029
 9030    cx.update(|cx| {
 9031        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9032            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9033                settings.auto_signature_help = Some(true);
 9034            });
 9035        });
 9036    });
 9037
 9038    let mut cx = EditorLspTestContext::new_rust(
 9039        lsp::ServerCapabilities {
 9040            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9041                ..Default::default()
 9042            }),
 9043            ..Default::default()
 9044        },
 9045        cx,
 9046    )
 9047    .await;
 9048
 9049    let language = Language::new(
 9050        LanguageConfig {
 9051            name: "Rust".into(),
 9052            brackets: BracketPairConfig {
 9053                pairs: vec![
 9054                    BracketPair {
 9055                        start: "{".to_string(),
 9056                        end: "}".to_string(),
 9057                        close: true,
 9058                        surround: true,
 9059                        newline: true,
 9060                    },
 9061                    BracketPair {
 9062                        start: "(".to_string(),
 9063                        end: ")".to_string(),
 9064                        close: true,
 9065                        surround: true,
 9066                        newline: true,
 9067                    },
 9068                    BracketPair {
 9069                        start: "/*".to_string(),
 9070                        end: " */".to_string(),
 9071                        close: true,
 9072                        surround: true,
 9073                        newline: true,
 9074                    },
 9075                    BracketPair {
 9076                        start: "[".to_string(),
 9077                        end: "]".to_string(),
 9078                        close: false,
 9079                        surround: false,
 9080                        newline: true,
 9081                    },
 9082                    BracketPair {
 9083                        start: "\"".to_string(),
 9084                        end: "\"".to_string(),
 9085                        close: true,
 9086                        surround: true,
 9087                        newline: false,
 9088                    },
 9089                    BracketPair {
 9090                        start: "<".to_string(),
 9091                        end: ">".to_string(),
 9092                        close: false,
 9093                        surround: true,
 9094                        newline: true,
 9095                    },
 9096                ],
 9097                ..Default::default()
 9098            },
 9099            autoclose_before: "})]".to_string(),
 9100            ..Default::default()
 9101        },
 9102        Some(tree_sitter_rust::LANGUAGE.into()),
 9103    );
 9104    let language = Arc::new(language);
 9105
 9106    cx.language_registry().add(language.clone());
 9107    cx.update_buffer(|buffer, cx| {
 9108        buffer.set_language(Some(language), cx);
 9109    });
 9110
 9111    cx.set_state(
 9112        &r#"
 9113            fn main() {
 9114                sampleˇ
 9115            }
 9116        "#
 9117        .unindent(),
 9118    );
 9119
 9120    cx.update_editor(|editor, window, cx| {
 9121        editor.handle_input("(", window, cx);
 9122    });
 9123    cx.assert_editor_state(
 9124        &"
 9125            fn main() {
 9126                sample(ˇ)
 9127            }
 9128        "
 9129        .unindent(),
 9130    );
 9131
 9132    let mocked_response = lsp::SignatureHelp {
 9133        signatures: vec![lsp::SignatureInformation {
 9134            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9135            documentation: None,
 9136            parameters: Some(vec![
 9137                lsp::ParameterInformation {
 9138                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9139                    documentation: None,
 9140                },
 9141                lsp::ParameterInformation {
 9142                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9143                    documentation: None,
 9144                },
 9145            ]),
 9146            active_parameter: None,
 9147        }],
 9148        active_signature: Some(0),
 9149        active_parameter: Some(0),
 9150    };
 9151    handle_signature_help_request(&mut cx, mocked_response).await;
 9152
 9153    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9154        .await;
 9155
 9156    cx.editor(|editor, _, _| {
 9157        let signature_help_state = editor.signature_help_state.popover().cloned();
 9158        assert_eq!(
 9159            signature_help_state.unwrap().label,
 9160            "param1: u8, param2: u8"
 9161        );
 9162    });
 9163}
 9164
 9165#[gpui::test]
 9166async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9167    init_test(cx, |_| {});
 9168
 9169    cx.update(|cx| {
 9170        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9171            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9172                settings.auto_signature_help = Some(false);
 9173                settings.show_signature_help_after_edits = Some(false);
 9174            });
 9175        });
 9176    });
 9177
 9178    let mut cx = EditorLspTestContext::new_rust(
 9179        lsp::ServerCapabilities {
 9180            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9181                ..Default::default()
 9182            }),
 9183            ..Default::default()
 9184        },
 9185        cx,
 9186    )
 9187    .await;
 9188
 9189    let language = Language::new(
 9190        LanguageConfig {
 9191            name: "Rust".into(),
 9192            brackets: BracketPairConfig {
 9193                pairs: vec![
 9194                    BracketPair {
 9195                        start: "{".to_string(),
 9196                        end: "}".to_string(),
 9197                        close: true,
 9198                        surround: true,
 9199                        newline: true,
 9200                    },
 9201                    BracketPair {
 9202                        start: "(".to_string(),
 9203                        end: ")".to_string(),
 9204                        close: true,
 9205                        surround: true,
 9206                        newline: true,
 9207                    },
 9208                    BracketPair {
 9209                        start: "/*".to_string(),
 9210                        end: " */".to_string(),
 9211                        close: true,
 9212                        surround: true,
 9213                        newline: true,
 9214                    },
 9215                    BracketPair {
 9216                        start: "[".to_string(),
 9217                        end: "]".to_string(),
 9218                        close: false,
 9219                        surround: false,
 9220                        newline: true,
 9221                    },
 9222                    BracketPair {
 9223                        start: "\"".to_string(),
 9224                        end: "\"".to_string(),
 9225                        close: true,
 9226                        surround: true,
 9227                        newline: false,
 9228                    },
 9229                    BracketPair {
 9230                        start: "<".to_string(),
 9231                        end: ">".to_string(),
 9232                        close: false,
 9233                        surround: true,
 9234                        newline: true,
 9235                    },
 9236                ],
 9237                ..Default::default()
 9238            },
 9239            autoclose_before: "})]".to_string(),
 9240            ..Default::default()
 9241        },
 9242        Some(tree_sitter_rust::LANGUAGE.into()),
 9243    );
 9244    let language = Arc::new(language);
 9245
 9246    cx.language_registry().add(language.clone());
 9247    cx.update_buffer(|buffer, cx| {
 9248        buffer.set_language(Some(language), cx);
 9249    });
 9250
 9251    // Ensure that signature_help is not called when no signature help is enabled.
 9252    cx.set_state(
 9253        &r#"
 9254            fn main() {
 9255                sampleˇ
 9256            }
 9257        "#
 9258        .unindent(),
 9259    );
 9260    cx.update_editor(|editor, window, cx| {
 9261        editor.handle_input("(", window, cx);
 9262    });
 9263    cx.assert_editor_state(
 9264        &"
 9265            fn main() {
 9266                sample(ˇ)
 9267            }
 9268        "
 9269        .unindent(),
 9270    );
 9271    cx.editor(|editor, _, _| {
 9272        assert!(editor.signature_help_state.task().is_none());
 9273    });
 9274
 9275    let mocked_response = lsp::SignatureHelp {
 9276        signatures: vec![lsp::SignatureInformation {
 9277            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9278            documentation: None,
 9279            parameters: Some(vec![
 9280                lsp::ParameterInformation {
 9281                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9282                    documentation: None,
 9283                },
 9284                lsp::ParameterInformation {
 9285                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9286                    documentation: None,
 9287                },
 9288            ]),
 9289            active_parameter: None,
 9290        }],
 9291        active_signature: Some(0),
 9292        active_parameter: Some(0),
 9293    };
 9294
 9295    // Ensure that signature_help is called when enabled afte edits
 9296    cx.update(|_, cx| {
 9297        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9298            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9299                settings.auto_signature_help = Some(false);
 9300                settings.show_signature_help_after_edits = Some(true);
 9301            });
 9302        });
 9303    });
 9304    cx.set_state(
 9305        &r#"
 9306            fn main() {
 9307                sampleˇ
 9308            }
 9309        "#
 9310        .unindent(),
 9311    );
 9312    cx.update_editor(|editor, window, cx| {
 9313        editor.handle_input("(", window, cx);
 9314    });
 9315    cx.assert_editor_state(
 9316        &"
 9317            fn main() {
 9318                sample(ˇ)
 9319            }
 9320        "
 9321        .unindent(),
 9322    );
 9323    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9324    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9325        .await;
 9326    cx.update_editor(|editor, _, _| {
 9327        let signature_help_state = editor.signature_help_state.popover().cloned();
 9328        assert!(signature_help_state.is_some());
 9329        assert_eq!(
 9330            signature_help_state.unwrap().label,
 9331            "param1: u8, param2: u8"
 9332        );
 9333        editor.signature_help_state = SignatureHelpState::default();
 9334    });
 9335
 9336    // Ensure that signature_help is called when auto signature help override is enabled
 9337    cx.update(|_, cx| {
 9338        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9339            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9340                settings.auto_signature_help = Some(true);
 9341                settings.show_signature_help_after_edits = Some(false);
 9342            });
 9343        });
 9344    });
 9345    cx.set_state(
 9346        &r#"
 9347            fn main() {
 9348                sampleˇ
 9349            }
 9350        "#
 9351        .unindent(),
 9352    );
 9353    cx.update_editor(|editor, window, cx| {
 9354        editor.handle_input("(", window, cx);
 9355    });
 9356    cx.assert_editor_state(
 9357        &"
 9358            fn main() {
 9359                sample(ˇ)
 9360            }
 9361        "
 9362        .unindent(),
 9363    );
 9364    handle_signature_help_request(&mut cx, mocked_response).await;
 9365    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9366        .await;
 9367    cx.editor(|editor, _, _| {
 9368        let signature_help_state = editor.signature_help_state.popover().cloned();
 9369        assert!(signature_help_state.is_some());
 9370        assert_eq!(
 9371            signature_help_state.unwrap().label,
 9372            "param1: u8, param2: u8"
 9373        );
 9374    });
 9375}
 9376
 9377#[gpui::test]
 9378async fn test_signature_help(cx: &mut TestAppContext) {
 9379    init_test(cx, |_| {});
 9380    cx.update(|cx| {
 9381        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9382            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9383                settings.auto_signature_help = Some(true);
 9384            });
 9385        });
 9386    });
 9387
 9388    let mut cx = EditorLspTestContext::new_rust(
 9389        lsp::ServerCapabilities {
 9390            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9391                ..Default::default()
 9392            }),
 9393            ..Default::default()
 9394        },
 9395        cx,
 9396    )
 9397    .await;
 9398
 9399    // A test that directly calls `show_signature_help`
 9400    cx.update_editor(|editor, window, cx| {
 9401        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9402    });
 9403
 9404    let mocked_response = lsp::SignatureHelp {
 9405        signatures: vec![lsp::SignatureInformation {
 9406            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9407            documentation: None,
 9408            parameters: Some(vec![
 9409                lsp::ParameterInformation {
 9410                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9411                    documentation: None,
 9412                },
 9413                lsp::ParameterInformation {
 9414                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9415                    documentation: None,
 9416                },
 9417            ]),
 9418            active_parameter: None,
 9419        }],
 9420        active_signature: Some(0),
 9421        active_parameter: Some(0),
 9422    };
 9423    handle_signature_help_request(&mut cx, mocked_response).await;
 9424
 9425    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9426        .await;
 9427
 9428    cx.editor(|editor, _, _| {
 9429        let signature_help_state = editor.signature_help_state.popover().cloned();
 9430        assert!(signature_help_state.is_some());
 9431        assert_eq!(
 9432            signature_help_state.unwrap().label,
 9433            "param1: u8, param2: u8"
 9434        );
 9435    });
 9436
 9437    // When exiting outside from inside the brackets, `signature_help` is closed.
 9438    cx.set_state(indoc! {"
 9439        fn main() {
 9440            sample(ˇ);
 9441        }
 9442
 9443        fn sample(param1: u8, param2: u8) {}
 9444    "});
 9445
 9446    cx.update_editor(|editor, window, cx| {
 9447        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
 9448    });
 9449
 9450    let mocked_response = lsp::SignatureHelp {
 9451        signatures: Vec::new(),
 9452        active_signature: None,
 9453        active_parameter: None,
 9454    };
 9455    handle_signature_help_request(&mut cx, mocked_response).await;
 9456
 9457    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9458        .await;
 9459
 9460    cx.editor(|editor, _, _| {
 9461        assert!(!editor.signature_help_state.is_shown());
 9462    });
 9463
 9464    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
 9465    cx.set_state(indoc! {"
 9466        fn main() {
 9467            sample(ˇ);
 9468        }
 9469
 9470        fn sample(param1: u8, param2: u8) {}
 9471    "});
 9472
 9473    let mocked_response = lsp::SignatureHelp {
 9474        signatures: vec![lsp::SignatureInformation {
 9475            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9476            documentation: None,
 9477            parameters: Some(vec![
 9478                lsp::ParameterInformation {
 9479                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9480                    documentation: None,
 9481                },
 9482                lsp::ParameterInformation {
 9483                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9484                    documentation: None,
 9485                },
 9486            ]),
 9487            active_parameter: None,
 9488        }],
 9489        active_signature: Some(0),
 9490        active_parameter: Some(0),
 9491    };
 9492    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9493    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9494        .await;
 9495    cx.editor(|editor, _, _| {
 9496        assert!(editor.signature_help_state.is_shown());
 9497    });
 9498
 9499    // Restore the popover with more parameter input
 9500    cx.set_state(indoc! {"
 9501        fn main() {
 9502            sample(param1, param2ˇ);
 9503        }
 9504
 9505        fn sample(param1: u8, param2: u8) {}
 9506    "});
 9507
 9508    let mocked_response = lsp::SignatureHelp {
 9509        signatures: vec![lsp::SignatureInformation {
 9510            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9511            documentation: None,
 9512            parameters: Some(vec![
 9513                lsp::ParameterInformation {
 9514                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9515                    documentation: None,
 9516                },
 9517                lsp::ParameterInformation {
 9518                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9519                    documentation: None,
 9520                },
 9521            ]),
 9522            active_parameter: None,
 9523        }],
 9524        active_signature: Some(0),
 9525        active_parameter: Some(1),
 9526    };
 9527    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9528    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9529        .await;
 9530
 9531    // When selecting a range, the popover is gone.
 9532    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
 9533    cx.update_editor(|editor, window, cx| {
 9534        editor.change_selections(None, window, cx, |s| {
 9535            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9536        })
 9537    });
 9538    cx.assert_editor_state(indoc! {"
 9539        fn main() {
 9540            sample(param1, «ˇparam2»);
 9541        }
 9542
 9543        fn sample(param1: u8, param2: u8) {}
 9544    "});
 9545    cx.editor(|editor, _, _| {
 9546        assert!(!editor.signature_help_state.is_shown());
 9547    });
 9548
 9549    // When unselecting again, the popover is back if within the brackets.
 9550    cx.update_editor(|editor, window, cx| {
 9551        editor.change_selections(None, window, cx, |s| {
 9552            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9553        })
 9554    });
 9555    cx.assert_editor_state(indoc! {"
 9556        fn main() {
 9557            sample(param1, ˇparam2);
 9558        }
 9559
 9560        fn sample(param1: u8, param2: u8) {}
 9561    "});
 9562    handle_signature_help_request(&mut cx, mocked_response).await;
 9563    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9564        .await;
 9565    cx.editor(|editor, _, _| {
 9566        assert!(editor.signature_help_state.is_shown());
 9567    });
 9568
 9569    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
 9570    cx.update_editor(|editor, window, cx| {
 9571        editor.change_selections(None, window, cx, |s| {
 9572            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
 9573            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9574        })
 9575    });
 9576    cx.assert_editor_state(indoc! {"
 9577        fn main() {
 9578            sample(param1, ˇparam2);
 9579        }
 9580
 9581        fn sample(param1: u8, param2: u8) {}
 9582    "});
 9583
 9584    let mocked_response = lsp::SignatureHelp {
 9585        signatures: vec![lsp::SignatureInformation {
 9586            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9587            documentation: None,
 9588            parameters: Some(vec![
 9589                lsp::ParameterInformation {
 9590                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9591                    documentation: None,
 9592                },
 9593                lsp::ParameterInformation {
 9594                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9595                    documentation: None,
 9596                },
 9597            ]),
 9598            active_parameter: None,
 9599        }],
 9600        active_signature: Some(0),
 9601        active_parameter: Some(1),
 9602    };
 9603    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9604    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9605        .await;
 9606    cx.update_editor(|editor, _, cx| {
 9607        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
 9608    });
 9609    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9610        .await;
 9611    cx.update_editor(|editor, window, cx| {
 9612        editor.change_selections(None, window, cx, |s| {
 9613            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9614        })
 9615    });
 9616    cx.assert_editor_state(indoc! {"
 9617        fn main() {
 9618            sample(param1, «ˇparam2»);
 9619        }
 9620
 9621        fn sample(param1: u8, param2: u8) {}
 9622    "});
 9623    cx.update_editor(|editor, window, cx| {
 9624        editor.change_selections(None, window, cx, |s| {
 9625            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9626        })
 9627    });
 9628    cx.assert_editor_state(indoc! {"
 9629        fn main() {
 9630            sample(param1, ˇparam2);
 9631        }
 9632
 9633        fn sample(param1: u8, param2: u8) {}
 9634    "});
 9635    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
 9636        .await;
 9637}
 9638
 9639#[gpui::test]
 9640async fn test_completion_mode(cx: &mut TestAppContext) {
 9641    init_test(cx, |_| {});
 9642    let mut cx = EditorLspTestContext::new_rust(
 9643        lsp::ServerCapabilities {
 9644            completion_provider: Some(lsp::CompletionOptions {
 9645                resolve_provider: Some(true),
 9646                ..Default::default()
 9647            }),
 9648            ..Default::default()
 9649        },
 9650        cx,
 9651    )
 9652    .await;
 9653
 9654    struct Run {
 9655        run_description: &'static str,
 9656        initial_state: String,
 9657        buffer_marked_text: String,
 9658        completion_text: &'static str,
 9659        expected_with_insert_mode: String,
 9660        expected_with_replace_mode: String,
 9661        expected_with_replace_subsequence_mode: String,
 9662        expected_with_replace_suffix_mode: String,
 9663    }
 9664
 9665    let runs = [
 9666        Run {
 9667            run_description: "Start of word matches completion text",
 9668            initial_state: "before ediˇ after".into(),
 9669            buffer_marked_text: "before <edi|> after".into(),
 9670            completion_text: "editor",
 9671            expected_with_insert_mode: "before editorˇ after".into(),
 9672            expected_with_replace_mode: "before editorˇ after".into(),
 9673            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9674            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9675        },
 9676        Run {
 9677            run_description: "Accept same text at the middle of the word",
 9678            initial_state: "before ediˇtor after".into(),
 9679            buffer_marked_text: "before <edi|tor> after".into(),
 9680            completion_text: "editor",
 9681            expected_with_insert_mode: "before editorˇtor after".into(),
 9682            expected_with_replace_mode: "before ediˇtor after".into(),
 9683            expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
 9684            expected_with_replace_suffix_mode: "before ediˇtor after".into(),
 9685        },
 9686        Run {
 9687            run_description: "End of word matches completion text -- cursor at end",
 9688            initial_state: "before torˇ after".into(),
 9689            buffer_marked_text: "before <tor|> after".into(),
 9690            completion_text: "editor",
 9691            expected_with_insert_mode: "before editorˇ after".into(),
 9692            expected_with_replace_mode: "before editorˇ after".into(),
 9693            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9694            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9695        },
 9696        Run {
 9697            run_description: "End of word matches completion text -- cursor at start",
 9698            initial_state: "before ˇtor after".into(),
 9699            buffer_marked_text: "before <|tor> after".into(),
 9700            completion_text: "editor",
 9701            expected_with_insert_mode: "before editorˇtor after".into(),
 9702            expected_with_replace_mode: "before editorˇ after".into(),
 9703            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
 9704            expected_with_replace_suffix_mode: "before editorˇ after".into(),
 9705        },
 9706        Run {
 9707            run_description: "Prepend text containing whitespace",
 9708            initial_state: "pˇfield: bool".into(),
 9709            buffer_marked_text: "<p|field>: bool".into(),
 9710            completion_text: "pub ",
 9711            expected_with_insert_mode: "pub ˇfield: bool".into(),
 9712            expected_with_replace_mode: "pub ˇ: bool".into(),
 9713            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
 9714            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
 9715        },
 9716        Run {
 9717            run_description: "Add element to start of list",
 9718            initial_state: "[element_ˇelement_2]".into(),
 9719            buffer_marked_text: "[<element_|element_2>]".into(),
 9720            completion_text: "element_1",
 9721            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
 9722            expected_with_replace_mode: "[element_1ˇ]".into(),
 9723            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
 9724            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
 9725        },
 9726        Run {
 9727            run_description: "Add element to start of list -- first and second elements are equal",
 9728            initial_state: "[elˇelement]".into(),
 9729            buffer_marked_text: "[<el|element>]".into(),
 9730            completion_text: "element",
 9731            expected_with_insert_mode: "[elementˇelement]".into(),
 9732            expected_with_replace_mode: "[elˇement]".into(),
 9733            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
 9734            expected_with_replace_suffix_mode: "[elˇement]".into(),
 9735        },
 9736        Run {
 9737            run_description: "Ends with matching suffix",
 9738            initial_state: "SubˇError".into(),
 9739            buffer_marked_text: "<Sub|Error>".into(),
 9740            completion_text: "SubscriptionError",
 9741            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
 9742            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9743            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9744            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
 9745        },
 9746        Run {
 9747            run_description: "Suffix is a subsequence -- contiguous",
 9748            initial_state: "SubˇErr".into(),
 9749            buffer_marked_text: "<Sub|Err>".into(),
 9750            completion_text: "SubscriptionError",
 9751            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
 9752            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9753            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9754            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
 9755        },
 9756        Run {
 9757            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
 9758            initial_state: "Suˇscrirr".into(),
 9759            buffer_marked_text: "<Su|scrirr>".into(),
 9760            completion_text: "SubscriptionError",
 9761            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
 9762            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
 9763            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
 9764            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
 9765        },
 9766        Run {
 9767            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
 9768            initial_state: "foo(indˇix)".into(),
 9769            buffer_marked_text: "foo(<ind|ix>)".into(),
 9770            completion_text: "node_index",
 9771            expected_with_insert_mode: "foo(node_indexˇix)".into(),
 9772            expected_with_replace_mode: "foo(node_indexˇ)".into(),
 9773            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
 9774            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
 9775        },
 9776    ];
 9777
 9778    for run in runs {
 9779        let run_variations = [
 9780            (LspInsertMode::Insert, run.expected_with_insert_mode),
 9781            (LspInsertMode::Replace, run.expected_with_replace_mode),
 9782            (
 9783                LspInsertMode::ReplaceSubsequence,
 9784                run.expected_with_replace_subsequence_mode,
 9785            ),
 9786            (
 9787                LspInsertMode::ReplaceSuffix,
 9788                run.expected_with_replace_suffix_mode,
 9789            ),
 9790        ];
 9791
 9792        for (lsp_insert_mode, expected_text) in run_variations {
 9793            eprintln!(
 9794                "run = {:?}, mode = {lsp_insert_mode:.?}",
 9795                run.run_description,
 9796            );
 9797
 9798            update_test_language_settings(&mut cx, |settings| {
 9799                settings.defaults.completions = Some(CompletionSettings {
 9800                    lsp_insert_mode,
 9801                    words: WordsCompletionMode::Disabled,
 9802                    lsp: true,
 9803                    lsp_fetch_timeout_ms: 0,
 9804                });
 9805            });
 9806
 9807            cx.set_state(&run.initial_state);
 9808            cx.update_editor(|editor, window, cx| {
 9809                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9810            });
 9811
 9812            let counter = Arc::new(AtomicUsize::new(0));
 9813            handle_completion_request_with_insert_and_replace(
 9814                &mut cx,
 9815                &run.buffer_marked_text,
 9816                vec![run.completion_text],
 9817                counter.clone(),
 9818            )
 9819            .await;
 9820            cx.condition(|editor, _| editor.context_menu_visible())
 9821                .await;
 9822            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9823
 9824            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9825                editor
 9826                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9827                    .unwrap()
 9828            });
 9829            cx.assert_editor_state(&expected_text);
 9830            handle_resolve_completion_request(&mut cx, None).await;
 9831            apply_additional_edits.await.unwrap();
 9832        }
 9833    }
 9834}
 9835
 9836#[gpui::test]
 9837async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
 9838    init_test(cx, |_| {});
 9839    let mut cx = EditorLspTestContext::new_rust(
 9840        lsp::ServerCapabilities {
 9841            completion_provider: Some(lsp::CompletionOptions {
 9842                resolve_provider: Some(true),
 9843                ..Default::default()
 9844            }),
 9845            ..Default::default()
 9846        },
 9847        cx,
 9848    )
 9849    .await;
 9850
 9851    let initial_state = "SubˇError";
 9852    let buffer_marked_text = "<Sub|Error>";
 9853    let completion_text = "SubscriptionError";
 9854    let expected_with_insert_mode = "SubscriptionErrorˇError";
 9855    let expected_with_replace_mode = "SubscriptionErrorˇ";
 9856
 9857    update_test_language_settings(&mut cx, |settings| {
 9858        settings.defaults.completions = Some(CompletionSettings {
 9859            words: WordsCompletionMode::Disabled,
 9860            // set the opposite here to ensure that the action is overriding the default behavior
 9861            lsp_insert_mode: LspInsertMode::Insert,
 9862            lsp: true,
 9863            lsp_fetch_timeout_ms: 0,
 9864        });
 9865    });
 9866
 9867    cx.set_state(initial_state);
 9868    cx.update_editor(|editor, window, cx| {
 9869        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9870    });
 9871
 9872    let counter = Arc::new(AtomicUsize::new(0));
 9873    handle_completion_request_with_insert_and_replace(
 9874        &mut cx,
 9875        &buffer_marked_text,
 9876        vec![completion_text],
 9877        counter.clone(),
 9878    )
 9879    .await;
 9880    cx.condition(|editor, _| editor.context_menu_visible())
 9881        .await;
 9882    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9883
 9884    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9885        editor
 9886            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
 9887            .unwrap()
 9888    });
 9889    cx.assert_editor_state(&expected_with_replace_mode);
 9890    handle_resolve_completion_request(&mut cx, None).await;
 9891    apply_additional_edits.await.unwrap();
 9892
 9893    update_test_language_settings(&mut cx, |settings| {
 9894        settings.defaults.completions = Some(CompletionSettings {
 9895            words: WordsCompletionMode::Disabled,
 9896            // set the opposite here to ensure that the action is overriding the default behavior
 9897            lsp_insert_mode: LspInsertMode::Replace,
 9898            lsp: true,
 9899            lsp_fetch_timeout_ms: 0,
 9900        });
 9901    });
 9902
 9903    cx.set_state(initial_state);
 9904    cx.update_editor(|editor, window, cx| {
 9905        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9906    });
 9907    handle_completion_request_with_insert_and_replace(
 9908        &mut cx,
 9909        &buffer_marked_text,
 9910        vec![completion_text],
 9911        counter.clone(),
 9912    )
 9913    .await;
 9914    cx.condition(|editor, _| editor.context_menu_visible())
 9915        .await;
 9916    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
 9917
 9918    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9919        editor
 9920            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
 9921            .unwrap()
 9922    });
 9923    cx.assert_editor_state(&expected_with_insert_mode);
 9924    handle_resolve_completion_request(&mut cx, None).await;
 9925    apply_additional_edits.await.unwrap();
 9926}
 9927
 9928#[gpui::test]
 9929async fn test_completion(cx: &mut TestAppContext) {
 9930    init_test(cx, |_| {});
 9931
 9932    let mut cx = EditorLspTestContext::new_rust(
 9933        lsp::ServerCapabilities {
 9934            completion_provider: Some(lsp::CompletionOptions {
 9935                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9936                resolve_provider: Some(true),
 9937                ..Default::default()
 9938            }),
 9939            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9940            ..Default::default()
 9941        },
 9942        cx,
 9943    )
 9944    .await;
 9945    let counter = Arc::new(AtomicUsize::new(0));
 9946
 9947    cx.set_state(indoc! {"
 9948        oneˇ
 9949        two
 9950        three
 9951    "});
 9952    cx.simulate_keystroke(".");
 9953    handle_completion_request(
 9954        &mut cx,
 9955        indoc! {"
 9956            one.|<>
 9957            two
 9958            three
 9959        "},
 9960        vec!["first_completion", "second_completion"],
 9961        counter.clone(),
 9962    )
 9963    .await;
 9964    cx.condition(|editor, _| editor.context_menu_visible())
 9965        .await;
 9966    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9967
 9968    let _handler = handle_signature_help_request(
 9969        &mut cx,
 9970        lsp::SignatureHelp {
 9971            signatures: vec![lsp::SignatureInformation {
 9972                label: "test signature".to_string(),
 9973                documentation: None,
 9974                parameters: Some(vec![lsp::ParameterInformation {
 9975                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
 9976                    documentation: None,
 9977                }]),
 9978                active_parameter: None,
 9979            }],
 9980            active_signature: None,
 9981            active_parameter: None,
 9982        },
 9983    );
 9984    cx.update_editor(|editor, window, cx| {
 9985        assert!(
 9986            !editor.signature_help_state.is_shown(),
 9987            "No signature help was called for"
 9988        );
 9989        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9990    });
 9991    cx.run_until_parked();
 9992    cx.update_editor(|editor, _, _| {
 9993        assert!(
 9994            !editor.signature_help_state.is_shown(),
 9995            "No signature help should be shown when completions menu is open"
 9996        );
 9997    });
 9998
 9999    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10000        editor.context_menu_next(&Default::default(), window, cx);
10001        editor
10002            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10003            .unwrap()
10004    });
10005    cx.assert_editor_state(indoc! {"
10006        one.second_completionˇ
10007        two
10008        three
10009    "});
10010
10011    handle_resolve_completion_request(
10012        &mut cx,
10013        Some(vec![
10014            (
10015                //This overlaps with the primary completion edit which is
10016                //misbehavior from the LSP spec, test that we filter it out
10017                indoc! {"
10018                    one.second_ˇcompletion
10019                    two
10020                    threeˇ
10021                "},
10022                "overlapping additional edit",
10023            ),
10024            (
10025                indoc! {"
10026                    one.second_completion
10027                    two
10028                    threeˇ
10029                "},
10030                "\nadditional edit",
10031            ),
10032        ]),
10033    )
10034    .await;
10035    apply_additional_edits.await.unwrap();
10036    cx.assert_editor_state(indoc! {"
10037        one.second_completionˇ
10038        two
10039        three
10040        additional edit
10041    "});
10042
10043    cx.set_state(indoc! {"
10044        one.second_completion
10045        twoˇ
10046        threeˇ
10047        additional edit
10048    "});
10049    cx.simulate_keystroke(" ");
10050    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10051    cx.simulate_keystroke("s");
10052    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10053
10054    cx.assert_editor_state(indoc! {"
10055        one.second_completion
10056        two sˇ
10057        three sˇ
10058        additional edit
10059    "});
10060    handle_completion_request(
10061        &mut cx,
10062        indoc! {"
10063            one.second_completion
10064            two s
10065            three <s|>
10066            additional edit
10067        "},
10068        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10069        counter.clone(),
10070    )
10071    .await;
10072    cx.condition(|editor, _| editor.context_menu_visible())
10073        .await;
10074    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10075
10076    cx.simulate_keystroke("i");
10077
10078    handle_completion_request(
10079        &mut cx,
10080        indoc! {"
10081            one.second_completion
10082            two si
10083            three <si|>
10084            additional edit
10085        "},
10086        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
10087        counter.clone(),
10088    )
10089    .await;
10090    cx.condition(|editor, _| editor.context_menu_visible())
10091        .await;
10092    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
10093
10094    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10095        editor
10096            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10097            .unwrap()
10098    });
10099    cx.assert_editor_state(indoc! {"
10100        one.second_completion
10101        two sixth_completionˇ
10102        three sixth_completionˇ
10103        additional edit
10104    "});
10105
10106    apply_additional_edits.await.unwrap();
10107
10108    update_test_language_settings(&mut cx, |settings| {
10109        settings.defaults.show_completions_on_input = Some(false);
10110    });
10111    cx.set_state("editorˇ");
10112    cx.simulate_keystroke(".");
10113    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10114    cx.simulate_keystrokes("c l o");
10115    cx.assert_editor_state("editor.cloˇ");
10116    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
10117    cx.update_editor(|editor, window, cx| {
10118        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10119    });
10120    handle_completion_request(
10121        &mut cx,
10122        "editor.<clo|>",
10123        vec!["close", "clobber"],
10124        counter.clone(),
10125    )
10126    .await;
10127    cx.condition(|editor, _| editor.context_menu_visible())
10128        .await;
10129    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
10130
10131    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10132        editor
10133            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10134            .unwrap()
10135    });
10136    cx.assert_editor_state("editor.closeˇ");
10137    handle_resolve_completion_request(&mut cx, None).await;
10138    apply_additional_edits.await.unwrap();
10139}
10140
10141#[gpui::test]
10142async fn test_word_completion(cx: &mut TestAppContext) {
10143    let lsp_fetch_timeout_ms = 10;
10144    init_test(cx, |language_settings| {
10145        language_settings.defaults.completions = Some(CompletionSettings {
10146            words: WordsCompletionMode::Fallback,
10147            lsp: true,
10148            lsp_fetch_timeout_ms: 10,
10149            lsp_insert_mode: LspInsertMode::Insert,
10150        });
10151    });
10152
10153    let mut cx = EditorLspTestContext::new_rust(
10154        lsp::ServerCapabilities {
10155            completion_provider: Some(lsp::CompletionOptions {
10156                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10157                ..lsp::CompletionOptions::default()
10158            }),
10159            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10160            ..lsp::ServerCapabilities::default()
10161        },
10162        cx,
10163    )
10164    .await;
10165
10166    let throttle_completions = Arc::new(AtomicBool::new(false));
10167
10168    let lsp_throttle_completions = throttle_completions.clone();
10169    let _completion_requests_handler =
10170        cx.lsp
10171            .server
10172            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
10173                let lsp_throttle_completions = lsp_throttle_completions.clone();
10174                let cx = cx.clone();
10175                async move {
10176                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
10177                        cx.background_executor()
10178                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
10179                            .await;
10180                    }
10181                    Ok(Some(lsp::CompletionResponse::Array(vec![
10182                        lsp::CompletionItem {
10183                            label: "first".into(),
10184                            ..lsp::CompletionItem::default()
10185                        },
10186                        lsp::CompletionItem {
10187                            label: "last".into(),
10188                            ..lsp::CompletionItem::default()
10189                        },
10190                    ])))
10191                }
10192            });
10193
10194    cx.set_state(indoc! {"
10195        oneˇ
10196        two
10197        three
10198    "});
10199    cx.simulate_keystroke(".");
10200    cx.executor().run_until_parked();
10201    cx.condition(|editor, _| editor.context_menu_visible())
10202        .await;
10203    cx.update_editor(|editor, window, cx| {
10204        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10205        {
10206            assert_eq!(
10207                completion_menu_entries(&menu),
10208                &["first", "last"],
10209                "When LSP server is fast to reply, no fallback word completions are used"
10210            );
10211        } else {
10212            panic!("expected completion menu to be open");
10213        }
10214        editor.cancel(&Cancel, window, cx);
10215    });
10216    cx.executor().run_until_parked();
10217    cx.condition(|editor, _| !editor.context_menu_visible())
10218        .await;
10219
10220    throttle_completions.store(true, atomic::Ordering::Release);
10221    cx.simulate_keystroke(".");
10222    cx.executor()
10223        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
10224    cx.executor().run_until_parked();
10225    cx.condition(|editor, _| editor.context_menu_visible())
10226        .await;
10227    cx.update_editor(|editor, _, _| {
10228        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10229        {
10230            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
10231                "When LSP server is slow, document words can be shown instead, if configured accordingly");
10232        } else {
10233            panic!("expected completion menu to be open");
10234        }
10235    });
10236}
10237
10238#[gpui::test]
10239async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
10240    init_test(cx, |language_settings| {
10241        language_settings.defaults.completions = Some(CompletionSettings {
10242            words: WordsCompletionMode::Enabled,
10243            lsp: true,
10244            lsp_fetch_timeout_ms: 0,
10245            lsp_insert_mode: LspInsertMode::Insert,
10246        });
10247    });
10248
10249    let mut cx = EditorLspTestContext::new_rust(
10250        lsp::ServerCapabilities {
10251            completion_provider: Some(lsp::CompletionOptions {
10252                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10253                ..lsp::CompletionOptions::default()
10254            }),
10255            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10256            ..lsp::ServerCapabilities::default()
10257        },
10258        cx,
10259    )
10260    .await;
10261
10262    let _completion_requests_handler =
10263        cx.lsp
10264            .server
10265            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10266                Ok(Some(lsp::CompletionResponse::Array(vec![
10267                    lsp::CompletionItem {
10268                        label: "first".into(),
10269                        ..lsp::CompletionItem::default()
10270                    },
10271                    lsp::CompletionItem {
10272                        label: "last".into(),
10273                        ..lsp::CompletionItem::default()
10274                    },
10275                ])))
10276            });
10277
10278    cx.set_state(indoc! {"ˇ
10279        first
10280        last
10281        second
10282    "});
10283    cx.simulate_keystroke(".");
10284    cx.executor().run_until_parked();
10285    cx.condition(|editor, _| editor.context_menu_visible())
10286        .await;
10287    cx.update_editor(|editor, _, _| {
10288        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10289        {
10290            assert_eq!(
10291                completion_menu_entries(&menu),
10292                &["first", "last", "second"],
10293                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
10294            );
10295        } else {
10296            panic!("expected completion menu to be open");
10297        }
10298    });
10299}
10300
10301#[gpui::test]
10302async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
10303    init_test(cx, |language_settings| {
10304        language_settings.defaults.completions = Some(CompletionSettings {
10305            words: WordsCompletionMode::Disabled,
10306            lsp: true,
10307            lsp_fetch_timeout_ms: 0,
10308            lsp_insert_mode: LspInsertMode::Insert,
10309        });
10310    });
10311
10312    let mut cx = EditorLspTestContext::new_rust(
10313        lsp::ServerCapabilities {
10314            completion_provider: Some(lsp::CompletionOptions {
10315                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10316                ..lsp::CompletionOptions::default()
10317            }),
10318            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10319            ..lsp::ServerCapabilities::default()
10320        },
10321        cx,
10322    )
10323    .await;
10324
10325    let _completion_requests_handler =
10326        cx.lsp
10327            .server
10328            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10329                panic!("LSP completions should not be queried when dealing with word completions")
10330            });
10331
10332    cx.set_state(indoc! {"ˇ
10333        first
10334        last
10335        second
10336    "});
10337    cx.update_editor(|editor, window, cx| {
10338        editor.show_word_completions(&ShowWordCompletions, window, cx);
10339    });
10340    cx.executor().run_until_parked();
10341    cx.condition(|editor, _| editor.context_menu_visible())
10342        .await;
10343    cx.update_editor(|editor, _, _| {
10344        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10345        {
10346            assert_eq!(
10347                completion_menu_entries(&menu),
10348                &["first", "last", "second"],
10349                "`ShowWordCompletions` action should show word completions"
10350            );
10351        } else {
10352            panic!("expected completion menu to be open");
10353        }
10354    });
10355
10356    cx.simulate_keystroke("l");
10357    cx.executor().run_until_parked();
10358    cx.condition(|editor, _| editor.context_menu_visible())
10359        .await;
10360    cx.update_editor(|editor, _, _| {
10361        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10362        {
10363            assert_eq!(
10364                completion_menu_entries(&menu),
10365                &["last"],
10366                "After showing word completions, further editing should filter them and not query the LSP"
10367            );
10368        } else {
10369            panic!("expected completion menu to be open");
10370        }
10371    });
10372}
10373
10374#[gpui::test]
10375async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
10376    init_test(cx, |language_settings| {
10377        language_settings.defaults.completions = Some(CompletionSettings {
10378            words: WordsCompletionMode::Fallback,
10379            lsp: false,
10380            lsp_fetch_timeout_ms: 0,
10381            lsp_insert_mode: LspInsertMode::Insert,
10382        });
10383    });
10384
10385    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10386
10387    cx.set_state(indoc! {"ˇ
10388        0_usize
10389        let
10390        33
10391        4.5f32
10392    "});
10393    cx.update_editor(|editor, window, cx| {
10394        editor.show_completions(&ShowCompletions::default(), window, cx);
10395    });
10396    cx.executor().run_until_parked();
10397    cx.condition(|editor, _| editor.context_menu_visible())
10398        .await;
10399    cx.update_editor(|editor, window, cx| {
10400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10401        {
10402            assert_eq!(
10403                completion_menu_entries(&menu),
10404                &["let"],
10405                "With no digits in the completion query, no digits should be in the word completions"
10406            );
10407        } else {
10408            panic!("expected completion menu to be open");
10409        }
10410        editor.cancel(&Cancel, window, cx);
10411    });
10412
10413    cx.set_state(indoc! {"10414        0_usize
10415        let
10416        3
10417        33.35f32
10418    "});
10419    cx.update_editor(|editor, window, cx| {
10420        editor.show_completions(&ShowCompletions::default(), window, cx);
10421    });
10422    cx.executor().run_until_parked();
10423    cx.condition(|editor, _| editor.context_menu_visible())
10424        .await;
10425    cx.update_editor(|editor, _, _| {
10426        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10427        {
10428            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
10429                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
10430        } else {
10431            panic!("expected completion menu to be open");
10432        }
10433    });
10434}
10435
10436fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
10437    let position = || lsp::Position {
10438        line: params.text_document_position.position.line,
10439        character: params.text_document_position.position.character,
10440    };
10441    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10442        range: lsp::Range {
10443            start: position(),
10444            end: position(),
10445        },
10446        new_text: text.to_string(),
10447    }))
10448}
10449
10450#[gpui::test]
10451async fn test_multiline_completion(cx: &mut TestAppContext) {
10452    init_test(cx, |_| {});
10453
10454    let fs = FakeFs::new(cx.executor());
10455    fs.insert_tree(
10456        path!("/a"),
10457        json!({
10458            "main.ts": "a",
10459        }),
10460    )
10461    .await;
10462
10463    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10464    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10465    let typescript_language = Arc::new(Language::new(
10466        LanguageConfig {
10467            name: "TypeScript".into(),
10468            matcher: LanguageMatcher {
10469                path_suffixes: vec!["ts".to_string()],
10470                ..LanguageMatcher::default()
10471            },
10472            line_comments: vec!["// ".into()],
10473            ..LanguageConfig::default()
10474        },
10475        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10476    ));
10477    language_registry.add(typescript_language.clone());
10478    let mut fake_servers = language_registry.register_fake_lsp(
10479        "TypeScript",
10480        FakeLspAdapter {
10481            capabilities: lsp::ServerCapabilities {
10482                completion_provider: Some(lsp::CompletionOptions {
10483                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10484                    ..lsp::CompletionOptions::default()
10485                }),
10486                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10487                ..lsp::ServerCapabilities::default()
10488            },
10489            // Emulate vtsls label generation
10490            label_for_completion: Some(Box::new(|item, _| {
10491                let text = if let Some(description) = item
10492                    .label_details
10493                    .as_ref()
10494                    .and_then(|label_details| label_details.description.as_ref())
10495                {
10496                    format!("{} {}", item.label, description)
10497                } else if let Some(detail) = &item.detail {
10498                    format!("{} {}", item.label, detail)
10499                } else {
10500                    item.label.clone()
10501                };
10502                let len = text.len();
10503                Some(language::CodeLabel {
10504                    text,
10505                    runs: Vec::new(),
10506                    filter_range: 0..len,
10507                })
10508            })),
10509            ..FakeLspAdapter::default()
10510        },
10511    );
10512    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10513    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10514    let worktree_id = workspace
10515        .update(cx, |workspace, _window, cx| {
10516            workspace.project().update(cx, |project, cx| {
10517                project.worktrees(cx).next().unwrap().read(cx).id()
10518            })
10519        })
10520        .unwrap();
10521    let _buffer = project
10522        .update(cx, |project, cx| {
10523            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
10524        })
10525        .await
10526        .unwrap();
10527    let editor = workspace
10528        .update(cx, |workspace, window, cx| {
10529            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
10530        })
10531        .unwrap()
10532        .await
10533        .unwrap()
10534        .downcast::<Editor>()
10535        .unwrap();
10536    let fake_server = fake_servers.next().await.unwrap();
10537
10538    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
10539    let multiline_label_2 = "a\nb\nc\n";
10540    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
10541    let multiline_description = "d\ne\nf\n";
10542    let multiline_detail_2 = "g\nh\ni\n";
10543
10544    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
10545        move |params, _| async move {
10546            Ok(Some(lsp::CompletionResponse::Array(vec![
10547                lsp::CompletionItem {
10548                    label: multiline_label.to_string(),
10549                    text_edit: gen_text_edit(&params, "new_text_1"),
10550                    ..lsp::CompletionItem::default()
10551                },
10552                lsp::CompletionItem {
10553                    label: "single line label 1".to_string(),
10554                    detail: Some(multiline_detail.to_string()),
10555                    text_edit: gen_text_edit(&params, "new_text_2"),
10556                    ..lsp::CompletionItem::default()
10557                },
10558                lsp::CompletionItem {
10559                    label: "single line label 2".to_string(),
10560                    label_details: Some(lsp::CompletionItemLabelDetails {
10561                        description: Some(multiline_description.to_string()),
10562                        detail: None,
10563                    }),
10564                    text_edit: gen_text_edit(&params, "new_text_2"),
10565                    ..lsp::CompletionItem::default()
10566                },
10567                lsp::CompletionItem {
10568                    label: multiline_label_2.to_string(),
10569                    detail: Some(multiline_detail_2.to_string()),
10570                    text_edit: gen_text_edit(&params, "new_text_3"),
10571                    ..lsp::CompletionItem::default()
10572                },
10573                lsp::CompletionItem {
10574                    label: "Label with many     spaces and \t but without newlines".to_string(),
10575                    detail: Some(
10576                        "Details with many     spaces and \t but without newlines".to_string(),
10577                    ),
10578                    text_edit: gen_text_edit(&params, "new_text_4"),
10579                    ..lsp::CompletionItem::default()
10580                },
10581            ])))
10582        },
10583    );
10584
10585    editor.update_in(cx, |editor, window, cx| {
10586        cx.focus_self(window);
10587        editor.move_to_end(&MoveToEnd, window, cx);
10588        editor.handle_input(".", window, cx);
10589    });
10590    cx.run_until_parked();
10591    completion_handle.next().await.unwrap();
10592
10593    editor.update(cx, |editor, _| {
10594        assert!(editor.context_menu_visible());
10595        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10596        {
10597            let completion_labels = menu
10598                .completions
10599                .borrow()
10600                .iter()
10601                .map(|c| c.label.text.clone())
10602                .collect::<Vec<_>>();
10603            assert_eq!(
10604                completion_labels,
10605                &[
10606                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
10607                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
10608                    "single line label 2 d e f ",
10609                    "a b c g h i ",
10610                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
10611                ],
10612                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
10613            );
10614
10615            for completion in menu
10616                .completions
10617                .borrow()
10618                .iter() {
10619                    assert_eq!(
10620                        completion.label.filter_range,
10621                        0..completion.label.text.len(),
10622                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
10623                    );
10624                }
10625        } else {
10626            panic!("expected completion menu to be open");
10627        }
10628    });
10629}
10630
10631#[gpui::test]
10632async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
10633    init_test(cx, |_| {});
10634    let mut cx = EditorLspTestContext::new_rust(
10635        lsp::ServerCapabilities {
10636            completion_provider: Some(lsp::CompletionOptions {
10637                trigger_characters: Some(vec![".".to_string()]),
10638                ..Default::default()
10639            }),
10640            ..Default::default()
10641        },
10642        cx,
10643    )
10644    .await;
10645    cx.lsp
10646        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10647            Ok(Some(lsp::CompletionResponse::Array(vec![
10648                lsp::CompletionItem {
10649                    label: "first".into(),
10650                    ..Default::default()
10651                },
10652                lsp::CompletionItem {
10653                    label: "last".into(),
10654                    ..Default::default()
10655                },
10656            ])))
10657        });
10658    cx.set_state("variableˇ");
10659    cx.simulate_keystroke(".");
10660    cx.executor().run_until_parked();
10661
10662    cx.update_editor(|editor, _, _| {
10663        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10664        {
10665            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
10666        } else {
10667            panic!("expected completion menu to be open");
10668        }
10669    });
10670
10671    cx.update_editor(|editor, window, cx| {
10672        editor.move_page_down(&MovePageDown::default(), window, cx);
10673        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10674        {
10675            assert!(
10676                menu.selected_item == 1,
10677                "expected PageDown to select the last item from the context menu"
10678            );
10679        } else {
10680            panic!("expected completion menu to stay open after PageDown");
10681        }
10682    });
10683
10684    cx.update_editor(|editor, window, cx| {
10685        editor.move_page_up(&MovePageUp::default(), window, cx);
10686        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10687        {
10688            assert!(
10689                menu.selected_item == 0,
10690                "expected PageUp to select the first item from the context menu"
10691            );
10692        } else {
10693            panic!("expected completion menu to stay open after PageUp");
10694        }
10695    });
10696}
10697
10698#[gpui::test]
10699async fn test_completion_sort(cx: &mut TestAppContext) {
10700    init_test(cx, |_| {});
10701    let mut cx = EditorLspTestContext::new_rust(
10702        lsp::ServerCapabilities {
10703            completion_provider: Some(lsp::CompletionOptions {
10704                trigger_characters: Some(vec![".".to_string()]),
10705                ..Default::default()
10706            }),
10707            ..Default::default()
10708        },
10709        cx,
10710    )
10711    .await;
10712    cx.lsp
10713        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10714            Ok(Some(lsp::CompletionResponse::Array(vec![
10715                lsp::CompletionItem {
10716                    label: "Range".into(),
10717                    sort_text: Some("a".into()),
10718                    ..Default::default()
10719                },
10720                lsp::CompletionItem {
10721                    label: "r".into(),
10722                    sort_text: Some("b".into()),
10723                    ..Default::default()
10724                },
10725                lsp::CompletionItem {
10726                    label: "ret".into(),
10727                    sort_text: Some("c".into()),
10728                    ..Default::default()
10729                },
10730                lsp::CompletionItem {
10731                    label: "return".into(),
10732                    sort_text: Some("d".into()),
10733                    ..Default::default()
10734                },
10735                lsp::CompletionItem {
10736                    label: "slice".into(),
10737                    sort_text: Some("d".into()),
10738                    ..Default::default()
10739                },
10740            ])))
10741        });
10742    cx.set_state("");
10743    cx.executor().run_until_parked();
10744    cx.update_editor(|editor, window, cx| {
10745        editor.show_completions(
10746            &ShowCompletions {
10747                trigger: Some("r".into()),
10748            },
10749            window,
10750            cx,
10751        );
10752    });
10753    cx.executor().run_until_parked();
10754
10755    cx.update_editor(|editor, _, _| {
10756        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10757        {
10758            assert_eq!(
10759                completion_menu_entries(&menu),
10760                &["r", "ret", "Range", "return"]
10761            );
10762        } else {
10763            panic!("expected completion menu to be open");
10764        }
10765    });
10766}
10767
10768#[gpui::test]
10769async fn test_as_is_completions(cx: &mut TestAppContext) {
10770    init_test(cx, |_| {});
10771    let mut cx = EditorLspTestContext::new_rust(
10772        lsp::ServerCapabilities {
10773            completion_provider: Some(lsp::CompletionOptions {
10774                ..Default::default()
10775            }),
10776            ..Default::default()
10777        },
10778        cx,
10779    )
10780    .await;
10781    cx.lsp
10782        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10783            Ok(Some(lsp::CompletionResponse::Array(vec![
10784                lsp::CompletionItem {
10785                    label: "unsafe".into(),
10786                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10787                        range: lsp::Range {
10788                            start: lsp::Position {
10789                                line: 1,
10790                                character: 2,
10791                            },
10792                            end: lsp::Position {
10793                                line: 1,
10794                                character: 3,
10795                            },
10796                        },
10797                        new_text: "unsafe".to_string(),
10798                    })),
10799                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
10800                    ..Default::default()
10801                },
10802            ])))
10803        });
10804    cx.set_state("fn a() {}\n");
10805    cx.executor().run_until_parked();
10806    cx.update_editor(|editor, window, cx| {
10807        editor.show_completions(
10808            &ShowCompletions {
10809                trigger: Some("\n".into()),
10810            },
10811            window,
10812            cx,
10813        );
10814    });
10815    cx.executor().run_until_parked();
10816
10817    cx.update_editor(|editor, window, cx| {
10818        editor.confirm_completion(&Default::default(), window, cx)
10819    });
10820    cx.executor().run_until_parked();
10821    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
10822}
10823
10824#[gpui::test]
10825async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
10826    init_test(cx, |_| {});
10827
10828    let mut cx = EditorLspTestContext::new_rust(
10829        lsp::ServerCapabilities {
10830            completion_provider: Some(lsp::CompletionOptions {
10831                trigger_characters: Some(vec![".".to_string()]),
10832                resolve_provider: Some(true),
10833                ..Default::default()
10834            }),
10835            ..Default::default()
10836        },
10837        cx,
10838    )
10839    .await;
10840
10841    cx.set_state("fn main() { let a = 2ˇ; }");
10842    cx.simulate_keystroke(".");
10843    let completion_item = lsp::CompletionItem {
10844        label: "Some".into(),
10845        kind: Some(lsp::CompletionItemKind::SNIPPET),
10846        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10847        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10848            kind: lsp::MarkupKind::Markdown,
10849            value: "```rust\nSome(2)\n```".to_string(),
10850        })),
10851        deprecated: Some(false),
10852        sort_text: Some("Some".to_string()),
10853        filter_text: Some("Some".to_string()),
10854        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10855        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10856            range: lsp::Range {
10857                start: lsp::Position {
10858                    line: 0,
10859                    character: 22,
10860                },
10861                end: lsp::Position {
10862                    line: 0,
10863                    character: 22,
10864                },
10865            },
10866            new_text: "Some(2)".to_string(),
10867        })),
10868        additional_text_edits: Some(vec![lsp::TextEdit {
10869            range: lsp::Range {
10870                start: lsp::Position {
10871                    line: 0,
10872                    character: 20,
10873                },
10874                end: lsp::Position {
10875                    line: 0,
10876                    character: 22,
10877                },
10878            },
10879            new_text: "".to_string(),
10880        }]),
10881        ..Default::default()
10882    };
10883
10884    let closure_completion_item = completion_item.clone();
10885    let counter = Arc::new(AtomicUsize::new(0));
10886    let counter_clone = counter.clone();
10887    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
10888        let task_completion_item = closure_completion_item.clone();
10889        counter_clone.fetch_add(1, atomic::Ordering::Release);
10890        async move {
10891            Ok(Some(lsp::CompletionResponse::Array(vec![
10892                task_completion_item,
10893            ])))
10894        }
10895    });
10896
10897    cx.condition(|editor, _| editor.context_menu_visible())
10898        .await;
10899    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
10900    assert!(request.next().await.is_some());
10901    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10902
10903    cx.simulate_keystrokes("S o m");
10904    cx.condition(|editor, _| editor.context_menu_visible())
10905        .await;
10906    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
10907    assert!(request.next().await.is_some());
10908    assert!(request.next().await.is_some());
10909    assert!(request.next().await.is_some());
10910    request.close();
10911    assert!(request.next().await.is_none());
10912    assert_eq!(
10913        counter.load(atomic::Ordering::Acquire),
10914        4,
10915        "With the completions menu open, only one LSP request should happen per input"
10916    );
10917}
10918
10919#[gpui::test]
10920async fn test_toggle_comment(cx: &mut TestAppContext) {
10921    init_test(cx, |_| {});
10922    let mut cx = EditorTestContext::new(cx).await;
10923    let language = Arc::new(Language::new(
10924        LanguageConfig {
10925            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10926            ..Default::default()
10927        },
10928        Some(tree_sitter_rust::LANGUAGE.into()),
10929    ));
10930    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10931
10932    // If multiple selections intersect a line, the line is only toggled once.
10933    cx.set_state(indoc! {"
10934        fn a() {
10935            «//b();
10936            ˇ»// «c();
10937            //ˇ»  d();
10938        }
10939    "});
10940
10941    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10942
10943    cx.assert_editor_state(indoc! {"
10944        fn a() {
10945            «b();
10946            c();
10947            ˇ» d();
10948        }
10949    "});
10950
10951    // The comment prefix is inserted at the same column for every line in a
10952    // selection.
10953    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10954
10955    cx.assert_editor_state(indoc! {"
10956        fn a() {
10957            // «b();
10958            // c();
10959            ˇ»//  d();
10960        }
10961    "});
10962
10963    // If a selection ends at the beginning of a line, that line is not toggled.
10964    cx.set_selections_state(indoc! {"
10965        fn a() {
10966            // b();
10967            «// c();
10968        ˇ»    //  d();
10969        }
10970    "});
10971
10972    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10973
10974    cx.assert_editor_state(indoc! {"
10975        fn a() {
10976            // b();
10977            «c();
10978        ˇ»    //  d();
10979        }
10980    "});
10981
10982    // If a selection span a single line and is empty, the line is toggled.
10983    cx.set_state(indoc! {"
10984        fn a() {
10985            a();
10986            b();
10987        ˇ
10988        }
10989    "});
10990
10991    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10992
10993    cx.assert_editor_state(indoc! {"
10994        fn a() {
10995            a();
10996            b();
10997        //•ˇ
10998        }
10999    "});
11000
11001    // If a selection span multiple lines, empty lines are not toggled.
11002    cx.set_state(indoc! {"
11003        fn a() {
11004            «a();
11005
11006            c();ˇ»
11007        }
11008    "});
11009
11010    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11011
11012    cx.assert_editor_state(indoc! {"
11013        fn a() {
11014            // «a();
11015
11016            // c();ˇ»
11017        }
11018    "});
11019
11020    // If a selection includes multiple comment prefixes, all lines are uncommented.
11021    cx.set_state(indoc! {"
11022        fn a() {
11023            «// a();
11024            /// b();
11025            //! c();ˇ»
11026        }
11027    "});
11028
11029    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11030
11031    cx.assert_editor_state(indoc! {"
11032        fn a() {
11033            «a();
11034            b();
11035            c();ˇ»
11036        }
11037    "});
11038}
11039
11040#[gpui::test]
11041async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11042    init_test(cx, |_| {});
11043    let mut cx = EditorTestContext::new(cx).await;
11044    let language = Arc::new(Language::new(
11045        LanguageConfig {
11046            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11047            ..Default::default()
11048        },
11049        Some(tree_sitter_rust::LANGUAGE.into()),
11050    ));
11051    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11052
11053    let toggle_comments = &ToggleComments {
11054        advance_downwards: false,
11055        ignore_indent: true,
11056    };
11057
11058    // If multiple selections intersect a line, the line is only toggled once.
11059    cx.set_state(indoc! {"
11060        fn a() {
11061        //    «b();
11062        //    c();
11063        //    ˇ» d();
11064        }
11065    "});
11066
11067    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11068
11069    cx.assert_editor_state(indoc! {"
11070        fn a() {
11071            «b();
11072            c();
11073            ˇ» d();
11074        }
11075    "});
11076
11077    // The comment prefix is inserted at the beginning of each line
11078    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11079
11080    cx.assert_editor_state(indoc! {"
11081        fn a() {
11082        //    «b();
11083        //    c();
11084        //    ˇ» d();
11085        }
11086    "});
11087
11088    // If a selection ends at the beginning of a line, that line is not toggled.
11089    cx.set_selections_state(indoc! {"
11090        fn a() {
11091        //    b();
11092        //    «c();
11093        ˇ»//     d();
11094        }
11095    "});
11096
11097    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11098
11099    cx.assert_editor_state(indoc! {"
11100        fn a() {
11101        //    b();
11102            «c();
11103        ˇ»//     d();
11104        }
11105    "});
11106
11107    // If a selection span a single line and is empty, the line is toggled.
11108    cx.set_state(indoc! {"
11109        fn a() {
11110            a();
11111            b();
11112        ˇ
11113        }
11114    "});
11115
11116    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11117
11118    cx.assert_editor_state(indoc! {"
11119        fn a() {
11120            a();
11121            b();
11122        //ˇ
11123        }
11124    "});
11125
11126    // If a selection span multiple lines, empty lines are not toggled.
11127    cx.set_state(indoc! {"
11128        fn a() {
11129            «a();
11130
11131            c();ˇ»
11132        }
11133    "});
11134
11135    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11136
11137    cx.assert_editor_state(indoc! {"
11138        fn a() {
11139        //    «a();
11140
11141        //    c();ˇ»
11142        }
11143    "});
11144
11145    // If a selection includes multiple comment prefixes, all lines are uncommented.
11146    cx.set_state(indoc! {"
11147        fn a() {
11148        //    «a();
11149        ///    b();
11150        //!    c();ˇ»
11151        }
11152    "});
11153
11154    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11155
11156    cx.assert_editor_state(indoc! {"
11157        fn a() {
11158            «a();
11159            b();
11160            c();ˇ»
11161        }
11162    "});
11163}
11164
11165#[gpui::test]
11166async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
11167    init_test(cx, |_| {});
11168
11169    let language = Arc::new(Language::new(
11170        LanguageConfig {
11171            line_comments: vec!["// ".into()],
11172            ..Default::default()
11173        },
11174        Some(tree_sitter_rust::LANGUAGE.into()),
11175    ));
11176
11177    let mut cx = EditorTestContext::new(cx).await;
11178
11179    cx.language_registry().add(language.clone());
11180    cx.update_buffer(|buffer, cx| {
11181        buffer.set_language(Some(language), cx);
11182    });
11183
11184    let toggle_comments = &ToggleComments {
11185        advance_downwards: true,
11186        ignore_indent: false,
11187    };
11188
11189    // Single cursor on one line -> advance
11190    // Cursor moves horizontally 3 characters as well on non-blank line
11191    cx.set_state(indoc!(
11192        "fn a() {
11193             ˇdog();
11194             cat();
11195        }"
11196    ));
11197    cx.update_editor(|editor, window, cx| {
11198        editor.toggle_comments(toggle_comments, window, cx);
11199    });
11200    cx.assert_editor_state(indoc!(
11201        "fn a() {
11202             // dog();
11203             catˇ();
11204        }"
11205    ));
11206
11207    // Single selection on one line -> don't advance
11208    cx.set_state(indoc!(
11209        "fn a() {
11210             «dog()ˇ»;
11211             cat();
11212        }"
11213    ));
11214    cx.update_editor(|editor, window, cx| {
11215        editor.toggle_comments(toggle_comments, window, cx);
11216    });
11217    cx.assert_editor_state(indoc!(
11218        "fn a() {
11219             // «dog()ˇ»;
11220             cat();
11221        }"
11222    ));
11223
11224    // Multiple cursors on one line -> advance
11225    cx.set_state(indoc!(
11226        "fn a() {
11227             ˇdˇog();
11228             cat();
11229        }"
11230    ));
11231    cx.update_editor(|editor, window, cx| {
11232        editor.toggle_comments(toggle_comments, window, cx);
11233    });
11234    cx.assert_editor_state(indoc!(
11235        "fn a() {
11236             // dog();
11237             catˇ(ˇ);
11238        }"
11239    ));
11240
11241    // Multiple cursors on one line, with selection -> don't advance
11242    cx.set_state(indoc!(
11243        "fn a() {
11244             ˇdˇog«()ˇ»;
11245             cat();
11246        }"
11247    ));
11248    cx.update_editor(|editor, window, cx| {
11249        editor.toggle_comments(toggle_comments, window, cx);
11250    });
11251    cx.assert_editor_state(indoc!(
11252        "fn a() {
11253             // ˇdˇog«()ˇ»;
11254             cat();
11255        }"
11256    ));
11257
11258    // Single cursor on one line -> advance
11259    // Cursor moves to column 0 on blank line
11260    cx.set_state(indoc!(
11261        "fn a() {
11262             ˇdog();
11263
11264             cat();
11265        }"
11266    ));
11267    cx.update_editor(|editor, window, cx| {
11268        editor.toggle_comments(toggle_comments, window, cx);
11269    });
11270    cx.assert_editor_state(indoc!(
11271        "fn a() {
11272             // dog();
11273        ˇ
11274             cat();
11275        }"
11276    ));
11277
11278    // Single cursor on one line -> advance
11279    // Cursor starts and ends at column 0
11280    cx.set_state(indoc!(
11281        "fn a() {
11282         ˇ    dog();
11283             cat();
11284        }"
11285    ));
11286    cx.update_editor(|editor, window, cx| {
11287        editor.toggle_comments(toggle_comments, window, cx);
11288    });
11289    cx.assert_editor_state(indoc!(
11290        "fn a() {
11291             // dog();
11292         ˇ    cat();
11293        }"
11294    ));
11295}
11296
11297#[gpui::test]
11298async fn test_toggle_block_comment(cx: &mut TestAppContext) {
11299    init_test(cx, |_| {});
11300
11301    let mut cx = EditorTestContext::new(cx).await;
11302
11303    let html_language = Arc::new(
11304        Language::new(
11305            LanguageConfig {
11306                name: "HTML".into(),
11307                block_comment: Some(("<!-- ".into(), " -->".into())),
11308                ..Default::default()
11309            },
11310            Some(tree_sitter_html::LANGUAGE.into()),
11311        )
11312        .with_injection_query(
11313            r#"
11314            (script_element
11315                (raw_text) @injection.content
11316                (#set! injection.language "javascript"))
11317            "#,
11318        )
11319        .unwrap(),
11320    );
11321
11322    let javascript_language = Arc::new(Language::new(
11323        LanguageConfig {
11324            name: "JavaScript".into(),
11325            line_comments: vec!["// ".into()],
11326            ..Default::default()
11327        },
11328        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11329    ));
11330
11331    cx.language_registry().add(html_language.clone());
11332    cx.language_registry().add(javascript_language.clone());
11333    cx.update_buffer(|buffer, cx| {
11334        buffer.set_language(Some(html_language), cx);
11335    });
11336
11337    // Toggle comments for empty selections
11338    cx.set_state(
11339        &r#"
11340            <p>A</p>ˇ
11341            <p>B</p>ˇ
11342            <p>C</p>ˇ
11343        "#
11344        .unindent(),
11345    );
11346    cx.update_editor(|editor, window, cx| {
11347        editor.toggle_comments(&ToggleComments::default(), window, cx)
11348    });
11349    cx.assert_editor_state(
11350        &r#"
11351            <!-- <p>A</p>ˇ -->
11352            <!-- <p>B</p>ˇ -->
11353            <!-- <p>C</p>ˇ -->
11354        "#
11355        .unindent(),
11356    );
11357    cx.update_editor(|editor, window, cx| {
11358        editor.toggle_comments(&ToggleComments::default(), window, cx)
11359    });
11360    cx.assert_editor_state(
11361        &r#"
11362            <p>A</p>ˇ
11363            <p>B</p>ˇ
11364            <p>C</p>ˇ
11365        "#
11366        .unindent(),
11367    );
11368
11369    // Toggle comments for mixture of empty and non-empty selections, where
11370    // multiple selections occupy a given line.
11371    cx.set_state(
11372        &r#"
11373            <p>A«</p>
11374            <p>ˇ»B</p>ˇ
11375            <p>C«</p>
11376            <p>ˇ»D</p>ˇ
11377        "#
11378        .unindent(),
11379    );
11380
11381    cx.update_editor(|editor, window, cx| {
11382        editor.toggle_comments(&ToggleComments::default(), window, cx)
11383    });
11384    cx.assert_editor_state(
11385        &r#"
11386            <!-- <p>A«</p>
11387            <p>ˇ»B</p>ˇ -->
11388            <!-- <p>C«</p>
11389            <p>ˇ»D</p>ˇ -->
11390        "#
11391        .unindent(),
11392    );
11393    cx.update_editor(|editor, window, cx| {
11394        editor.toggle_comments(&ToggleComments::default(), window, cx)
11395    });
11396    cx.assert_editor_state(
11397        &r#"
11398            <p>A«</p>
11399            <p>ˇ»B</p>ˇ
11400            <p>C«</p>
11401            <p>ˇ»D</p>ˇ
11402        "#
11403        .unindent(),
11404    );
11405
11406    // Toggle comments when different languages are active for different
11407    // selections.
11408    cx.set_state(
11409        &r#"
11410            ˇ<script>
11411                ˇvar x = new Y();
11412            ˇ</script>
11413        "#
11414        .unindent(),
11415    );
11416    cx.executor().run_until_parked();
11417    cx.update_editor(|editor, window, cx| {
11418        editor.toggle_comments(&ToggleComments::default(), window, cx)
11419    });
11420    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
11421    // Uncommenting and commenting from this position brings in even more wrong artifacts.
11422    cx.assert_editor_state(
11423        &r#"
11424            <!-- ˇ<script> -->
11425                // ˇvar x = new Y();
11426            <!-- ˇ</script> -->
11427        "#
11428        .unindent(),
11429    );
11430}
11431
11432#[gpui::test]
11433fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
11434    init_test(cx, |_| {});
11435
11436    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11437    let multibuffer = cx.new(|cx| {
11438        let mut multibuffer = MultiBuffer::new(ReadWrite);
11439        multibuffer.push_excerpts(
11440            buffer.clone(),
11441            [
11442                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
11443                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
11444            ],
11445            cx,
11446        );
11447        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
11448        multibuffer
11449    });
11450
11451    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11452    editor.update_in(cx, |editor, window, cx| {
11453        assert_eq!(editor.text(cx), "aaaa\nbbbb");
11454        editor.change_selections(None, window, cx, |s| {
11455            s.select_ranges([
11456                Point::new(0, 0)..Point::new(0, 0),
11457                Point::new(1, 0)..Point::new(1, 0),
11458            ])
11459        });
11460
11461        editor.handle_input("X", window, cx);
11462        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
11463        assert_eq!(
11464            editor.selections.ranges(cx),
11465            [
11466                Point::new(0, 1)..Point::new(0, 1),
11467                Point::new(1, 1)..Point::new(1, 1),
11468            ]
11469        );
11470
11471        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
11472        editor.change_selections(None, window, cx, |s| {
11473            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
11474        });
11475        editor.backspace(&Default::default(), window, cx);
11476        assert_eq!(editor.text(cx), "Xa\nbbb");
11477        assert_eq!(
11478            editor.selections.ranges(cx),
11479            [Point::new(1, 0)..Point::new(1, 0)]
11480        );
11481
11482        editor.change_selections(None, window, cx, |s| {
11483            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
11484        });
11485        editor.backspace(&Default::default(), window, cx);
11486        assert_eq!(editor.text(cx), "X\nbb");
11487        assert_eq!(
11488            editor.selections.ranges(cx),
11489            [Point::new(0, 1)..Point::new(0, 1)]
11490        );
11491    });
11492}
11493
11494#[gpui::test]
11495fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
11496    init_test(cx, |_| {});
11497
11498    let markers = vec![('[', ']').into(), ('(', ')').into()];
11499    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
11500        indoc! {"
11501            [aaaa
11502            (bbbb]
11503            cccc)",
11504        },
11505        markers.clone(),
11506    );
11507    let excerpt_ranges = markers.into_iter().map(|marker| {
11508        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
11509        ExcerptRange::new(context.clone())
11510    });
11511    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
11512    let multibuffer = cx.new(|cx| {
11513        let mut multibuffer = MultiBuffer::new(ReadWrite);
11514        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
11515        multibuffer
11516    });
11517
11518    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
11519    editor.update_in(cx, |editor, window, cx| {
11520        let (expected_text, selection_ranges) = marked_text_ranges(
11521            indoc! {"
11522                aaaa
11523                bˇbbb
11524                bˇbbˇb
11525                cccc"
11526            },
11527            true,
11528        );
11529        assert_eq!(editor.text(cx), expected_text);
11530        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
11531
11532        editor.handle_input("X", window, cx);
11533
11534        let (expected_text, expected_selections) = marked_text_ranges(
11535            indoc! {"
11536                aaaa
11537                bXˇbbXb
11538                bXˇbbXˇb
11539                cccc"
11540            },
11541            false,
11542        );
11543        assert_eq!(editor.text(cx), expected_text);
11544        assert_eq!(editor.selections.ranges(cx), expected_selections);
11545
11546        editor.newline(&Newline, window, cx);
11547        let (expected_text, expected_selections) = marked_text_ranges(
11548            indoc! {"
11549                aaaa
11550                bX
11551                ˇbbX
11552                b
11553                bX
11554                ˇbbX
11555                ˇb
11556                cccc"
11557            },
11558            false,
11559        );
11560        assert_eq!(editor.text(cx), expected_text);
11561        assert_eq!(editor.selections.ranges(cx), expected_selections);
11562    });
11563}
11564
11565#[gpui::test]
11566fn test_refresh_selections(cx: &mut TestAppContext) {
11567    init_test(cx, |_| {});
11568
11569    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11570    let mut excerpt1_id = None;
11571    let multibuffer = cx.new(|cx| {
11572        let mut multibuffer = MultiBuffer::new(ReadWrite);
11573        excerpt1_id = multibuffer
11574            .push_excerpts(
11575                buffer.clone(),
11576                [
11577                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
11578                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
11579                ],
11580                cx,
11581            )
11582            .into_iter()
11583            .next();
11584        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
11585        multibuffer
11586    });
11587
11588    let editor = cx.add_window(|window, cx| {
11589        let mut editor = build_editor(multibuffer.clone(), window, cx);
11590        let snapshot = editor.snapshot(window, cx);
11591        editor.change_selections(None, window, cx, |s| {
11592            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
11593        });
11594        editor.begin_selection(
11595            Point::new(2, 1).to_display_point(&snapshot),
11596            true,
11597            1,
11598            window,
11599            cx,
11600        );
11601        assert_eq!(
11602            editor.selections.ranges(cx),
11603            [
11604                Point::new(1, 3)..Point::new(1, 3),
11605                Point::new(2, 1)..Point::new(2, 1),
11606            ]
11607        );
11608        editor
11609    });
11610
11611    // Refreshing selections is a no-op when excerpts haven't changed.
11612    _ = editor.update(cx, |editor, window, cx| {
11613        editor.change_selections(None, window, cx, |s| s.refresh());
11614        assert_eq!(
11615            editor.selections.ranges(cx),
11616            [
11617                Point::new(1, 3)..Point::new(1, 3),
11618                Point::new(2, 1)..Point::new(2, 1),
11619            ]
11620        );
11621    });
11622
11623    multibuffer.update(cx, |multibuffer, cx| {
11624        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11625    });
11626    _ = editor.update(cx, |editor, window, cx| {
11627        // Removing an excerpt causes the first selection to become degenerate.
11628        assert_eq!(
11629            editor.selections.ranges(cx),
11630            [
11631                Point::new(0, 0)..Point::new(0, 0),
11632                Point::new(0, 1)..Point::new(0, 1)
11633            ]
11634        );
11635
11636        // Refreshing selections will relocate the first selection to the original buffer
11637        // location.
11638        editor.change_selections(None, window, cx, |s| s.refresh());
11639        assert_eq!(
11640            editor.selections.ranges(cx),
11641            [
11642                Point::new(0, 1)..Point::new(0, 1),
11643                Point::new(0, 3)..Point::new(0, 3)
11644            ]
11645        );
11646        assert!(editor.selections.pending_anchor().is_some());
11647    });
11648}
11649
11650#[gpui::test]
11651fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
11652    init_test(cx, |_| {});
11653
11654    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
11655    let mut excerpt1_id = None;
11656    let multibuffer = cx.new(|cx| {
11657        let mut multibuffer = MultiBuffer::new(ReadWrite);
11658        excerpt1_id = multibuffer
11659            .push_excerpts(
11660                buffer.clone(),
11661                [
11662                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
11663                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
11664                ],
11665                cx,
11666            )
11667            .into_iter()
11668            .next();
11669        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
11670        multibuffer
11671    });
11672
11673    let editor = cx.add_window(|window, cx| {
11674        let mut editor = build_editor(multibuffer.clone(), window, cx);
11675        let snapshot = editor.snapshot(window, cx);
11676        editor.begin_selection(
11677            Point::new(1, 3).to_display_point(&snapshot),
11678            false,
11679            1,
11680            window,
11681            cx,
11682        );
11683        assert_eq!(
11684            editor.selections.ranges(cx),
11685            [Point::new(1, 3)..Point::new(1, 3)]
11686        );
11687        editor
11688    });
11689
11690    multibuffer.update(cx, |multibuffer, cx| {
11691        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11692    });
11693    _ = editor.update(cx, |editor, window, cx| {
11694        assert_eq!(
11695            editor.selections.ranges(cx),
11696            [Point::new(0, 0)..Point::new(0, 0)]
11697        );
11698
11699        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
11700        editor.change_selections(None, window, cx, |s| s.refresh());
11701        assert_eq!(
11702            editor.selections.ranges(cx),
11703            [Point::new(0, 3)..Point::new(0, 3)]
11704        );
11705        assert!(editor.selections.pending_anchor().is_some());
11706    });
11707}
11708
11709#[gpui::test]
11710async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
11711    init_test(cx, |_| {});
11712
11713    let language = Arc::new(
11714        Language::new(
11715            LanguageConfig {
11716                brackets: BracketPairConfig {
11717                    pairs: vec![
11718                        BracketPair {
11719                            start: "{".to_string(),
11720                            end: "}".to_string(),
11721                            close: true,
11722                            surround: true,
11723                            newline: true,
11724                        },
11725                        BracketPair {
11726                            start: "/* ".to_string(),
11727                            end: " */".to_string(),
11728                            close: true,
11729                            surround: true,
11730                            newline: true,
11731                        },
11732                    ],
11733                    ..Default::default()
11734                },
11735                ..Default::default()
11736            },
11737            Some(tree_sitter_rust::LANGUAGE.into()),
11738        )
11739        .with_indents_query("")
11740        .unwrap(),
11741    );
11742
11743    let text = concat!(
11744        "{   }\n",     //
11745        "  x\n",       //
11746        "  /*   */\n", //
11747        "x\n",         //
11748        "{{} }\n",     //
11749    );
11750
11751    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11752    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11753    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11754    editor
11755        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11756        .await;
11757
11758    editor.update_in(cx, |editor, window, cx| {
11759        editor.change_selections(None, window, cx, |s| {
11760            s.select_display_ranges([
11761                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
11762                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
11763                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
11764            ])
11765        });
11766        editor.newline(&Newline, window, cx);
11767
11768        assert_eq!(
11769            editor.buffer().read(cx).read(cx).text(),
11770            concat!(
11771                "{ \n",    // Suppress rustfmt
11772                "\n",      //
11773                "}\n",     //
11774                "  x\n",   //
11775                "  /* \n", //
11776                "  \n",    //
11777                "  */\n",  //
11778                "x\n",     //
11779                "{{} \n",  //
11780                "}\n",     //
11781            )
11782        );
11783    });
11784}
11785
11786#[gpui::test]
11787fn test_highlighted_ranges(cx: &mut TestAppContext) {
11788    init_test(cx, |_| {});
11789
11790    let editor = cx.add_window(|window, cx| {
11791        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
11792        build_editor(buffer.clone(), window, cx)
11793    });
11794
11795    _ = editor.update(cx, |editor, window, cx| {
11796        struct Type1;
11797        struct Type2;
11798
11799        let buffer = editor.buffer.read(cx).snapshot(cx);
11800
11801        let anchor_range =
11802            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
11803
11804        editor.highlight_background::<Type1>(
11805            &[
11806                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
11807                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
11808                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
11809                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
11810            ],
11811            |_| Hsla::red(),
11812            cx,
11813        );
11814        editor.highlight_background::<Type2>(
11815            &[
11816                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
11817                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
11818                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
11819                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
11820            ],
11821            |_| Hsla::green(),
11822            cx,
11823        );
11824
11825        let snapshot = editor.snapshot(window, cx);
11826        let mut highlighted_ranges = editor.background_highlights_in_range(
11827            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
11828            &snapshot,
11829            cx.theme().colors(),
11830        );
11831        // Enforce a consistent ordering based on color without relying on the ordering of the
11832        // highlight's `TypeId` which is non-executor.
11833        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
11834        assert_eq!(
11835            highlighted_ranges,
11836            &[
11837                (
11838                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
11839                    Hsla::red(),
11840                ),
11841                (
11842                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11843                    Hsla::red(),
11844                ),
11845                (
11846                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
11847                    Hsla::green(),
11848                ),
11849                (
11850                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11851                    Hsla::green(),
11852                ),
11853            ]
11854        );
11855        assert_eq!(
11856            editor.background_highlights_in_range(
11857                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11858                &snapshot,
11859                cx.theme().colors(),
11860            ),
11861            &[(
11862                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11863                Hsla::red(),
11864            )]
11865        );
11866    });
11867}
11868
11869#[gpui::test]
11870async fn test_following(cx: &mut TestAppContext) {
11871    init_test(cx, |_| {});
11872
11873    let fs = FakeFs::new(cx.executor());
11874    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11875
11876    let buffer = project.update(cx, |project, cx| {
11877        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11878        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11879    });
11880    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11881    let follower = cx.update(|cx| {
11882        cx.open_window(
11883            WindowOptions {
11884                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11885                    gpui::Point::new(px(0.), px(0.)),
11886                    gpui::Point::new(px(10.), px(80.)),
11887                ))),
11888                ..Default::default()
11889            },
11890            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11891        )
11892        .unwrap()
11893    });
11894
11895    let is_still_following = Rc::new(RefCell::new(true));
11896    let follower_edit_event_count = Rc::new(RefCell::new(0));
11897    let pending_update = Rc::new(RefCell::new(None));
11898    let leader_entity = leader.root(cx).unwrap();
11899    let follower_entity = follower.root(cx).unwrap();
11900    _ = follower.update(cx, {
11901        let update = pending_update.clone();
11902        let is_still_following = is_still_following.clone();
11903        let follower_edit_event_count = follower_edit_event_count.clone();
11904        |_, window, cx| {
11905            cx.subscribe_in(
11906                &leader_entity,
11907                window,
11908                move |_, leader, event, window, cx| {
11909                    leader.read(cx).add_event_to_update_proto(
11910                        event,
11911                        &mut update.borrow_mut(),
11912                        window,
11913                        cx,
11914                    );
11915                },
11916            )
11917            .detach();
11918
11919            cx.subscribe_in(
11920                &follower_entity,
11921                window,
11922                move |_, _, event: &EditorEvent, _window, _cx| {
11923                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11924                        *is_still_following.borrow_mut() = false;
11925                    }
11926
11927                    if let EditorEvent::BufferEdited = event {
11928                        *follower_edit_event_count.borrow_mut() += 1;
11929                    }
11930                },
11931            )
11932            .detach();
11933        }
11934    });
11935
11936    // Update the selections only
11937    _ = leader.update(cx, |leader, window, cx| {
11938        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11939    });
11940    follower
11941        .update(cx, |follower, window, cx| {
11942            follower.apply_update_proto(
11943                &project,
11944                pending_update.borrow_mut().take().unwrap(),
11945                window,
11946                cx,
11947            )
11948        })
11949        .unwrap()
11950        .await
11951        .unwrap();
11952    _ = follower.update(cx, |follower, _, cx| {
11953        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11954    });
11955    assert!(*is_still_following.borrow());
11956    assert_eq!(*follower_edit_event_count.borrow(), 0);
11957
11958    // Update the scroll position only
11959    _ = leader.update(cx, |leader, window, cx| {
11960        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11961    });
11962    follower
11963        .update(cx, |follower, window, cx| {
11964            follower.apply_update_proto(
11965                &project,
11966                pending_update.borrow_mut().take().unwrap(),
11967                window,
11968                cx,
11969            )
11970        })
11971        .unwrap()
11972        .await
11973        .unwrap();
11974    assert_eq!(
11975        follower
11976            .update(cx, |follower, _, cx| follower.scroll_position(cx))
11977            .unwrap(),
11978        gpui::Point::new(1.5, 3.5)
11979    );
11980    assert!(*is_still_following.borrow());
11981    assert_eq!(*follower_edit_event_count.borrow(), 0);
11982
11983    // Update the selections and scroll position. The follower's scroll position is updated
11984    // via autoscroll, not via the leader's exact scroll position.
11985    _ = leader.update(cx, |leader, window, cx| {
11986        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11987        leader.request_autoscroll(Autoscroll::newest(), cx);
11988        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11989    });
11990    follower
11991        .update(cx, |follower, window, cx| {
11992            follower.apply_update_proto(
11993                &project,
11994                pending_update.borrow_mut().take().unwrap(),
11995                window,
11996                cx,
11997            )
11998        })
11999        .unwrap()
12000        .await
12001        .unwrap();
12002    _ = follower.update(cx, |follower, _, cx| {
12003        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12004        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12005    });
12006    assert!(*is_still_following.borrow());
12007
12008    // Creating a pending selection that precedes another selection
12009    _ = leader.update(cx, |leader, window, cx| {
12010        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12011        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12012    });
12013    follower
12014        .update(cx, |follower, window, cx| {
12015            follower.apply_update_proto(
12016                &project,
12017                pending_update.borrow_mut().take().unwrap(),
12018                window,
12019                cx,
12020            )
12021        })
12022        .unwrap()
12023        .await
12024        .unwrap();
12025    _ = follower.update(cx, |follower, _, cx| {
12026        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12027    });
12028    assert!(*is_still_following.borrow());
12029
12030    // Extend the pending selection so that it surrounds another selection
12031    _ = leader.update(cx, |leader, window, cx| {
12032        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12033    });
12034    follower
12035        .update(cx, |follower, window, cx| {
12036            follower.apply_update_proto(
12037                &project,
12038                pending_update.borrow_mut().take().unwrap(),
12039                window,
12040                cx,
12041            )
12042        })
12043        .unwrap()
12044        .await
12045        .unwrap();
12046    _ = follower.update(cx, |follower, _, cx| {
12047        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12048    });
12049
12050    // Scrolling locally breaks the follow
12051    _ = follower.update(cx, |follower, window, cx| {
12052        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12053        follower.set_scroll_anchor(
12054            ScrollAnchor {
12055                anchor: top_anchor,
12056                offset: gpui::Point::new(0.0, 0.5),
12057            },
12058            window,
12059            cx,
12060        );
12061    });
12062    assert!(!(*is_still_following.borrow()));
12063}
12064
12065#[gpui::test]
12066async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12067    init_test(cx, |_| {});
12068
12069    let fs = FakeFs::new(cx.executor());
12070    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12072    let pane = workspace
12073        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12074        .unwrap();
12075
12076    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12077
12078    let leader = pane.update_in(cx, |_, window, cx| {
12079        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12080        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12081    });
12082
12083    // Start following the editor when it has no excerpts.
12084    let mut state_message =
12085        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12086    let workspace_entity = workspace.root(cx).unwrap();
12087    let follower_1 = cx
12088        .update_window(*workspace.deref(), |_, window, cx| {
12089            Editor::from_state_proto(
12090                workspace_entity,
12091                ViewId {
12092                    creator: Default::default(),
12093                    id: 0,
12094                },
12095                &mut state_message,
12096                window,
12097                cx,
12098            )
12099        })
12100        .unwrap()
12101        .unwrap()
12102        .await
12103        .unwrap();
12104
12105    let update_message = Rc::new(RefCell::new(None));
12106    follower_1.update_in(cx, {
12107        let update = update_message.clone();
12108        |_, window, cx| {
12109            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
12110                leader.read(cx).add_event_to_update_proto(
12111                    event,
12112                    &mut update.borrow_mut(),
12113                    window,
12114                    cx,
12115                );
12116            })
12117            .detach();
12118        }
12119    });
12120
12121    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
12122        (
12123            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
12124            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
12125        )
12126    });
12127
12128    // Insert some excerpts.
12129    leader.update(cx, |leader, cx| {
12130        leader.buffer.update(cx, |multibuffer, cx| {
12131            let excerpt_ids = multibuffer.push_excerpts(
12132                buffer_1.clone(),
12133                [
12134                    ExcerptRange::new(1..6),
12135                    ExcerptRange::new(12..15),
12136                    ExcerptRange::new(0..3),
12137                ],
12138                cx,
12139            );
12140            multibuffer.insert_excerpts_after(
12141                excerpt_ids[0],
12142                buffer_2.clone(),
12143                [ExcerptRange::new(8..12), ExcerptRange::new(0..6)],
12144                cx,
12145            );
12146        });
12147    });
12148
12149    // Apply the update of adding the excerpts.
12150    follower_1
12151        .update_in(cx, |follower, window, cx| {
12152            follower.apply_update_proto(
12153                &project,
12154                update_message.borrow().clone().unwrap(),
12155                window,
12156                cx,
12157            )
12158        })
12159        .await
12160        .unwrap();
12161    assert_eq!(
12162        follower_1.update(cx, |editor, cx| editor.text(cx)),
12163        leader.update(cx, |editor, cx| editor.text(cx))
12164    );
12165    update_message.borrow_mut().take();
12166
12167    // Start following separately after it already has excerpts.
12168    let mut state_message =
12169        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12170    let workspace_entity = workspace.root(cx).unwrap();
12171    let follower_2 = cx
12172        .update_window(*workspace.deref(), |_, window, cx| {
12173            Editor::from_state_proto(
12174                workspace_entity,
12175                ViewId {
12176                    creator: Default::default(),
12177                    id: 0,
12178                },
12179                &mut state_message,
12180                window,
12181                cx,
12182            )
12183        })
12184        .unwrap()
12185        .unwrap()
12186        .await
12187        .unwrap();
12188    assert_eq!(
12189        follower_2.update(cx, |editor, cx| editor.text(cx)),
12190        leader.update(cx, |editor, cx| editor.text(cx))
12191    );
12192
12193    // Remove some excerpts.
12194    leader.update(cx, |leader, cx| {
12195        leader.buffer.update(cx, |multibuffer, cx| {
12196            let excerpt_ids = multibuffer.excerpt_ids();
12197            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
12198            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
12199        });
12200    });
12201
12202    // Apply the update of removing the excerpts.
12203    follower_1
12204        .update_in(cx, |follower, window, cx| {
12205            follower.apply_update_proto(
12206                &project,
12207                update_message.borrow().clone().unwrap(),
12208                window,
12209                cx,
12210            )
12211        })
12212        .await
12213        .unwrap();
12214    follower_2
12215        .update_in(cx, |follower, window, cx| {
12216            follower.apply_update_proto(
12217                &project,
12218                update_message.borrow().clone().unwrap(),
12219                window,
12220                cx,
12221            )
12222        })
12223        .await
12224        .unwrap();
12225    update_message.borrow_mut().take();
12226    assert_eq!(
12227        follower_1.update(cx, |editor, cx| editor.text(cx)),
12228        leader.update(cx, |editor, cx| editor.text(cx))
12229    );
12230}
12231
12232#[gpui::test]
12233async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12234    init_test(cx, |_| {});
12235
12236    let mut cx = EditorTestContext::new(cx).await;
12237    let lsp_store =
12238        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12239
12240    cx.set_state(indoc! {"
12241        ˇfn func(abc def: i32) -> u32 {
12242        }
12243    "});
12244
12245    cx.update(|_, cx| {
12246        lsp_store.update(cx, |lsp_store, cx| {
12247            lsp_store
12248                .update_diagnostics(
12249                    LanguageServerId(0),
12250                    lsp::PublishDiagnosticsParams {
12251                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12252                        version: None,
12253                        diagnostics: vec![
12254                            lsp::Diagnostic {
12255                                range: lsp::Range::new(
12256                                    lsp::Position::new(0, 11),
12257                                    lsp::Position::new(0, 12),
12258                                ),
12259                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12260                                ..Default::default()
12261                            },
12262                            lsp::Diagnostic {
12263                                range: lsp::Range::new(
12264                                    lsp::Position::new(0, 12),
12265                                    lsp::Position::new(0, 15),
12266                                ),
12267                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12268                                ..Default::default()
12269                            },
12270                            lsp::Diagnostic {
12271                                range: lsp::Range::new(
12272                                    lsp::Position::new(0, 25),
12273                                    lsp::Position::new(0, 28),
12274                                ),
12275                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12276                                ..Default::default()
12277                            },
12278                        ],
12279                    },
12280                    &[],
12281                    cx,
12282                )
12283                .unwrap()
12284        });
12285    });
12286
12287    executor.run_until_parked();
12288
12289    cx.update_editor(|editor, window, cx| {
12290        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12291    });
12292
12293    cx.assert_editor_state(indoc! {"
12294        fn func(abc def: i32) -> ˇu32 {
12295        }
12296    "});
12297
12298    cx.update_editor(|editor, window, cx| {
12299        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12300    });
12301
12302    cx.assert_editor_state(indoc! {"
12303        fn func(abc ˇdef: i32) -> u32 {
12304        }
12305    "});
12306
12307    cx.update_editor(|editor, window, cx| {
12308        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12309    });
12310
12311    cx.assert_editor_state(indoc! {"
12312        fn func(abcˇ def: i32) -> u32 {
12313        }
12314    "});
12315
12316    cx.update_editor(|editor, window, cx| {
12317        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12318    });
12319
12320    cx.assert_editor_state(indoc! {"
12321        fn func(abc def: i32) -> ˇu32 {
12322        }
12323    "});
12324}
12325
12326#[gpui::test]
12327async fn cycle_through_same_place_diagnostics(
12328    executor: BackgroundExecutor,
12329    cx: &mut TestAppContext,
12330) {
12331    init_test(cx, |_| {});
12332
12333    let mut cx = EditorTestContext::new(cx).await;
12334    let lsp_store =
12335        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12336
12337    cx.set_state(indoc! {"
12338        ˇfn func(abc def: i32) -> u32 {
12339        }
12340    "});
12341
12342    cx.update(|_, cx| {
12343        lsp_store.update(cx, |lsp_store, cx| {
12344            lsp_store
12345                .update_diagnostics(
12346                    LanguageServerId(0),
12347                    lsp::PublishDiagnosticsParams {
12348                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12349                        version: None,
12350                        diagnostics: vec![
12351                            lsp::Diagnostic {
12352                                range: lsp::Range::new(
12353                                    lsp::Position::new(0, 11),
12354                                    lsp::Position::new(0, 12),
12355                                ),
12356                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12357                                ..Default::default()
12358                            },
12359                            lsp::Diagnostic {
12360                                range: lsp::Range::new(
12361                                    lsp::Position::new(0, 12),
12362                                    lsp::Position::new(0, 15),
12363                                ),
12364                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12365                                ..Default::default()
12366                            },
12367                            lsp::Diagnostic {
12368                                range: lsp::Range::new(
12369                                    lsp::Position::new(0, 12),
12370                                    lsp::Position::new(0, 15),
12371                                ),
12372                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12373                                ..Default::default()
12374                            },
12375                            lsp::Diagnostic {
12376                                range: lsp::Range::new(
12377                                    lsp::Position::new(0, 25),
12378                                    lsp::Position::new(0, 28),
12379                                ),
12380                                severity: Some(lsp::DiagnosticSeverity::ERROR),
12381                                ..Default::default()
12382                            },
12383                        ],
12384                    },
12385                    &[],
12386                    cx,
12387                )
12388                .unwrap()
12389        });
12390    });
12391    executor.run_until_parked();
12392
12393    //// Backward
12394
12395    // Fourth diagnostic
12396    cx.update_editor(|editor, window, cx| {
12397        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12398    });
12399    cx.assert_editor_state(indoc! {"
12400        fn func(abc def: i32) -> ˇu32 {
12401        }
12402    "});
12403
12404    // Third diagnostic
12405    cx.update_editor(|editor, window, cx| {
12406        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12407    });
12408    cx.assert_editor_state(indoc! {"
12409        fn func(abc ˇdef: i32) -> u32 {
12410        }
12411    "});
12412
12413    // Second diagnostic, same place
12414    cx.update_editor(|editor, window, cx| {
12415        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12416    });
12417    cx.assert_editor_state(indoc! {"
12418        fn func(abc ˇdef: i32) -> u32 {
12419        }
12420    "});
12421
12422    // First diagnostic
12423    cx.update_editor(|editor, window, cx| {
12424        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12425    });
12426    cx.assert_editor_state(indoc! {"
12427        fn func(abcˇ def: i32) -> u32 {
12428        }
12429    "});
12430
12431    // Wrapped over, fourth diagnostic
12432    cx.update_editor(|editor, window, cx| {
12433        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
12434    });
12435    cx.assert_editor_state(indoc! {"
12436        fn func(abc def: i32) -> ˇu32 {
12437        }
12438    "});
12439
12440    cx.update_editor(|editor, window, cx| {
12441        editor.move_to_beginning(&MoveToBeginning, window, cx);
12442    });
12443    cx.assert_editor_state(indoc! {"
12444        ˇfn func(abc def: i32) -> u32 {
12445        }
12446    "});
12447
12448    //// Forward
12449
12450    // First diagnostic
12451    cx.update_editor(|editor, window, cx| {
12452        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12453    });
12454    cx.assert_editor_state(indoc! {"
12455        fn func(abcˇ def: i32) -> u32 {
12456        }
12457    "});
12458
12459    // Second diagnostic
12460    cx.update_editor(|editor, window, cx| {
12461        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12462    });
12463    cx.assert_editor_state(indoc! {"
12464        fn func(abc ˇdef: i32) -> u32 {
12465        }
12466    "});
12467
12468    // Third diagnostic, same place
12469    cx.update_editor(|editor, window, cx| {
12470        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12471    });
12472    cx.assert_editor_state(indoc! {"
12473        fn func(abc ˇdef: i32) -> u32 {
12474        }
12475    "});
12476
12477    // Fourth diagnostic
12478    cx.update_editor(|editor, window, cx| {
12479        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12480    });
12481    cx.assert_editor_state(indoc! {"
12482        fn func(abc def: i32) -> ˇu32 {
12483        }
12484    "});
12485
12486    // Wrapped around, first diagnostic
12487    cx.update_editor(|editor, window, cx| {
12488        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12489    });
12490    cx.assert_editor_state(indoc! {"
12491        fn func(abcˇ def: i32) -> u32 {
12492        }
12493    "});
12494}
12495
12496#[gpui::test]
12497async fn active_diagnostics_dismiss_after_invalidation(
12498    executor: BackgroundExecutor,
12499    cx: &mut TestAppContext,
12500) {
12501    init_test(cx, |_| {});
12502
12503    let mut cx = EditorTestContext::new(cx).await;
12504    let lsp_store =
12505        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12506
12507    cx.set_state(indoc! {"
12508        ˇfn func(abc def: i32) -> u32 {
12509        }
12510    "});
12511
12512    let message = "Something's wrong!";
12513    cx.update(|_, cx| {
12514        lsp_store.update(cx, |lsp_store, cx| {
12515            lsp_store
12516                .update_diagnostics(
12517                    LanguageServerId(0),
12518                    lsp::PublishDiagnosticsParams {
12519                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12520                        version: None,
12521                        diagnostics: vec![lsp::Diagnostic {
12522                            range: lsp::Range::new(
12523                                lsp::Position::new(0, 11),
12524                                lsp::Position::new(0, 12),
12525                            ),
12526                            severity: Some(lsp::DiagnosticSeverity::ERROR),
12527                            message: message.to_string(),
12528                            ..Default::default()
12529                        }],
12530                    },
12531                    &[],
12532                    cx,
12533                )
12534                .unwrap()
12535        });
12536    });
12537    executor.run_until_parked();
12538
12539    cx.update_editor(|editor, window, cx| {
12540        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12541        assert_eq!(
12542            editor
12543                .active_diagnostics
12544                .as_ref()
12545                .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
12546            Some(message),
12547            "Should have a diagnostics group activated"
12548        );
12549    });
12550    cx.assert_editor_state(indoc! {"
12551        fn func(abcˇ def: i32) -> u32 {
12552        }
12553    "});
12554
12555    cx.update(|_, cx| {
12556        lsp_store.update(cx, |lsp_store, cx| {
12557            lsp_store
12558                .update_diagnostics(
12559                    LanguageServerId(0),
12560                    lsp::PublishDiagnosticsParams {
12561                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12562                        version: None,
12563                        diagnostics: Vec::new(),
12564                    },
12565                    &[],
12566                    cx,
12567                )
12568                .unwrap()
12569        });
12570    });
12571    executor.run_until_parked();
12572    cx.update_editor(|editor, _, _| {
12573        assert_eq!(
12574            editor.active_diagnostics, None,
12575            "After no diagnostics set to the editor, no diagnostics should be active"
12576        );
12577    });
12578    cx.assert_editor_state(indoc! {"
12579        fn func(abcˇ def: i32) -> u32 {
12580        }
12581    "});
12582
12583    cx.update_editor(|editor, window, cx| {
12584        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
12585        assert_eq!(
12586            editor.active_diagnostics, None,
12587            "Should be no diagnostics to go to and activate"
12588        );
12589    });
12590    cx.assert_editor_state(indoc! {"
12591        fn func(abcˇ def: i32) -> u32 {
12592        }
12593    "});
12594}
12595
12596#[gpui::test]
12597async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
12598    init_test(cx, |_| {});
12599
12600    let mut cx = EditorTestContext::new(cx).await;
12601
12602    cx.set_state(indoc! {"
12603        fn func(abˇc def: i32) -> u32 {
12604        }
12605    "});
12606    let lsp_store =
12607        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
12608
12609    cx.update(|_, cx| {
12610        lsp_store.update(cx, |lsp_store, cx| {
12611            lsp_store.update_diagnostics(
12612                LanguageServerId(0),
12613                lsp::PublishDiagnosticsParams {
12614                    uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
12615                    version: None,
12616                    diagnostics: vec![lsp::Diagnostic {
12617                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
12618                        severity: Some(lsp::DiagnosticSeverity::ERROR),
12619                        message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
12620                        ..Default::default()
12621                    }],
12622                },
12623                &[],
12624                cx,
12625            )
12626        })
12627    }).unwrap();
12628    cx.run_until_parked();
12629    cx.update_editor(|editor, window, cx| {
12630        hover_popover::hover(editor, &Default::default(), window, cx)
12631    });
12632    cx.run_until_parked();
12633    cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
12634}
12635
12636#[gpui::test]
12637async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
12638    init_test(cx, |_| {});
12639
12640    let mut cx = EditorTestContext::new(cx).await;
12641
12642    let diff_base = r#"
12643        use some::mod;
12644
12645        const A: u32 = 42;
12646
12647        fn main() {
12648            println!("hello");
12649
12650            println!("world");
12651        }
12652        "#
12653    .unindent();
12654
12655    // Edits are modified, removed, modified, added
12656    cx.set_state(
12657        &r#"
12658        use some::modified;
12659
12660        ˇ
12661        fn main() {
12662            println!("hello there");
12663
12664            println!("around the");
12665            println!("world");
12666        }
12667        "#
12668        .unindent(),
12669    );
12670
12671    cx.set_head_text(&diff_base);
12672    executor.run_until_parked();
12673
12674    cx.update_editor(|editor, window, cx| {
12675        //Wrap around the bottom of the buffer
12676        for _ in 0..3 {
12677            editor.go_to_next_hunk(&GoToHunk, window, cx);
12678        }
12679    });
12680
12681    cx.assert_editor_state(
12682        &r#"
12683        ˇuse some::modified;
12684
12685
12686        fn main() {
12687            println!("hello there");
12688
12689            println!("around the");
12690            println!("world");
12691        }
12692        "#
12693        .unindent(),
12694    );
12695
12696    cx.update_editor(|editor, window, cx| {
12697        //Wrap around the top of the buffer
12698        for _ in 0..2 {
12699            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12700        }
12701    });
12702
12703    cx.assert_editor_state(
12704        &r#"
12705        use some::modified;
12706
12707
12708        fn main() {
12709        ˇ    println!("hello there");
12710
12711            println!("around the");
12712            println!("world");
12713        }
12714        "#
12715        .unindent(),
12716    );
12717
12718    cx.update_editor(|editor, window, cx| {
12719        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12720    });
12721
12722    cx.assert_editor_state(
12723        &r#"
12724        use some::modified;
12725
12726        ˇ
12727        fn main() {
12728            println!("hello there");
12729
12730            println!("around the");
12731            println!("world");
12732        }
12733        "#
12734        .unindent(),
12735    );
12736
12737    cx.update_editor(|editor, window, cx| {
12738        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12739    });
12740
12741    cx.assert_editor_state(
12742        &r#"
12743        ˇuse some::modified;
12744
12745
12746        fn main() {
12747            println!("hello there");
12748
12749            println!("around the");
12750            println!("world");
12751        }
12752        "#
12753        .unindent(),
12754    );
12755
12756    cx.update_editor(|editor, window, cx| {
12757        for _ in 0..2 {
12758            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12759        }
12760    });
12761
12762    cx.assert_editor_state(
12763        &r#"
12764        use some::modified;
12765
12766
12767        fn main() {
12768        ˇ    println!("hello there");
12769
12770            println!("around the");
12771            println!("world");
12772        }
12773        "#
12774        .unindent(),
12775    );
12776
12777    cx.update_editor(|editor, window, cx| {
12778        editor.fold(&Fold, window, cx);
12779    });
12780
12781    cx.update_editor(|editor, window, cx| {
12782        editor.go_to_next_hunk(&GoToHunk, window, cx);
12783    });
12784
12785    cx.assert_editor_state(
12786        &r#"
12787        ˇuse some::modified;
12788
12789
12790        fn main() {
12791            println!("hello there");
12792
12793            println!("around the");
12794            println!("world");
12795        }
12796        "#
12797        .unindent(),
12798    );
12799}
12800
12801#[test]
12802fn test_split_words() {
12803    fn split(text: &str) -> Vec<&str> {
12804        split_words(text).collect()
12805    }
12806
12807    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
12808    assert_eq!(split("hello_world"), &["hello_", "world"]);
12809    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
12810    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
12811    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
12812    assert_eq!(split("helloworld"), &["helloworld"]);
12813
12814    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
12815}
12816
12817#[gpui::test]
12818async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
12819    init_test(cx, |_| {});
12820
12821    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
12822    let mut assert = |before, after| {
12823        let _state_context = cx.set_state(before);
12824        cx.run_until_parked();
12825        cx.update_editor(|editor, window, cx| {
12826            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
12827        });
12828        cx.run_until_parked();
12829        cx.assert_editor_state(after);
12830    };
12831
12832    // Outside bracket jumps to outside of matching bracket
12833    assert("console.logˇ(var);", "console.log(var)ˇ;");
12834    assert("console.log(var)ˇ;", "console.logˇ(var);");
12835
12836    // Inside bracket jumps to inside of matching bracket
12837    assert("console.log(ˇvar);", "console.log(varˇ);");
12838    assert("console.log(varˇ);", "console.log(ˇvar);");
12839
12840    // When outside a bracket and inside, favor jumping to the inside bracket
12841    assert(
12842        "console.log('foo', [1, 2, 3]ˇ);",
12843        "console.log(ˇ'foo', [1, 2, 3]);",
12844    );
12845    assert(
12846        "console.log(ˇ'foo', [1, 2, 3]);",
12847        "console.log('foo', [1, 2, 3]ˇ);",
12848    );
12849
12850    // Bias forward if two options are equally likely
12851    assert(
12852        "let result = curried_fun()ˇ();",
12853        "let result = curried_fun()()ˇ;",
12854    );
12855
12856    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12857    assert(
12858        indoc! {"
12859            function test() {
12860                console.log('test')ˇ
12861            }"},
12862        indoc! {"
12863            function test() {
12864                console.logˇ('test')
12865            }"},
12866    );
12867}
12868
12869#[gpui::test]
12870async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12871    init_test(cx, |_| {});
12872
12873    let fs = FakeFs::new(cx.executor());
12874    fs.insert_tree(
12875        path!("/a"),
12876        json!({
12877            "main.rs": "fn main() { let a = 5; }",
12878            "other.rs": "// Test file",
12879        }),
12880    )
12881    .await;
12882    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12883
12884    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12885    language_registry.add(Arc::new(Language::new(
12886        LanguageConfig {
12887            name: "Rust".into(),
12888            matcher: LanguageMatcher {
12889                path_suffixes: vec!["rs".to_string()],
12890                ..Default::default()
12891            },
12892            brackets: BracketPairConfig {
12893                pairs: vec![BracketPair {
12894                    start: "{".to_string(),
12895                    end: "}".to_string(),
12896                    close: true,
12897                    surround: true,
12898                    newline: true,
12899                }],
12900                disabled_scopes_by_bracket_ix: Vec::new(),
12901            },
12902            ..Default::default()
12903        },
12904        Some(tree_sitter_rust::LANGUAGE.into()),
12905    )));
12906    let mut fake_servers = language_registry.register_fake_lsp(
12907        "Rust",
12908        FakeLspAdapter {
12909            capabilities: lsp::ServerCapabilities {
12910                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12911                    first_trigger_character: "{".to_string(),
12912                    more_trigger_character: None,
12913                }),
12914                ..Default::default()
12915            },
12916            ..Default::default()
12917        },
12918    );
12919
12920    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12921
12922    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12923
12924    let worktree_id = workspace
12925        .update(cx, |workspace, _, cx| {
12926            workspace.project().update(cx, |project, cx| {
12927                project.worktrees(cx).next().unwrap().read(cx).id()
12928            })
12929        })
12930        .unwrap();
12931
12932    let buffer = project
12933        .update(cx, |project, cx| {
12934            project.open_local_buffer(path!("/a/main.rs"), cx)
12935        })
12936        .await
12937        .unwrap();
12938    let editor_handle = workspace
12939        .update(cx, |workspace, window, cx| {
12940            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12941        })
12942        .unwrap()
12943        .await
12944        .unwrap()
12945        .downcast::<Editor>()
12946        .unwrap();
12947
12948    cx.executor().start_waiting();
12949    let fake_server = fake_servers.next().await.unwrap();
12950
12951    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
12952        |params, _| async move {
12953            assert_eq!(
12954                params.text_document_position.text_document.uri,
12955                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12956            );
12957            assert_eq!(
12958                params.text_document_position.position,
12959                lsp::Position::new(0, 21),
12960            );
12961
12962            Ok(Some(vec![lsp::TextEdit {
12963                new_text: "]".to_string(),
12964                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12965            }]))
12966        },
12967    );
12968
12969    editor_handle.update_in(cx, |editor, window, cx| {
12970        window.focus(&editor.focus_handle(cx));
12971        editor.change_selections(None, window, cx, |s| {
12972            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12973        });
12974        editor.handle_input("{", window, cx);
12975    });
12976
12977    cx.executor().run_until_parked();
12978
12979    buffer.update(cx, |buffer, _| {
12980        assert_eq!(
12981            buffer.text(),
12982            "fn main() { let a = {5}; }",
12983            "No extra braces from on type formatting should appear in the buffer"
12984        )
12985    });
12986}
12987
12988#[gpui::test]
12989async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12990    init_test(cx, |_| {});
12991
12992    let fs = FakeFs::new(cx.executor());
12993    fs.insert_tree(
12994        path!("/a"),
12995        json!({
12996            "main.rs": "fn main() { let a = 5; }",
12997            "other.rs": "// Test file",
12998        }),
12999    )
13000    .await;
13001
13002    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13003
13004    let server_restarts = Arc::new(AtomicUsize::new(0));
13005    let closure_restarts = Arc::clone(&server_restarts);
13006    let language_server_name = "test language server";
13007    let language_name: LanguageName = "Rust".into();
13008
13009    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13010    language_registry.add(Arc::new(Language::new(
13011        LanguageConfig {
13012            name: language_name.clone(),
13013            matcher: LanguageMatcher {
13014                path_suffixes: vec!["rs".to_string()],
13015                ..Default::default()
13016            },
13017            ..Default::default()
13018        },
13019        Some(tree_sitter_rust::LANGUAGE.into()),
13020    )));
13021    let mut fake_servers = language_registry.register_fake_lsp(
13022        "Rust",
13023        FakeLspAdapter {
13024            name: language_server_name,
13025            initialization_options: Some(json!({
13026                "testOptionValue": true
13027            })),
13028            initializer: Some(Box::new(move |fake_server| {
13029                let task_restarts = Arc::clone(&closure_restarts);
13030                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13031                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13032                    futures::future::ready(Ok(()))
13033                });
13034            })),
13035            ..Default::default()
13036        },
13037    );
13038
13039    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13040    let _buffer = project
13041        .update(cx, |project, cx| {
13042            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13043        })
13044        .await
13045        .unwrap();
13046    let _fake_server = fake_servers.next().await.unwrap();
13047    update_test_language_settings(cx, |language_settings| {
13048        language_settings.languages.insert(
13049            language_name.clone(),
13050            LanguageSettingsContent {
13051                tab_size: NonZeroU32::new(8),
13052                ..Default::default()
13053            },
13054        );
13055    });
13056    cx.executor().run_until_parked();
13057    assert_eq!(
13058        server_restarts.load(atomic::Ordering::Acquire),
13059        0,
13060        "Should not restart LSP server on an unrelated change"
13061    );
13062
13063    update_test_project_settings(cx, |project_settings| {
13064        project_settings.lsp.insert(
13065            "Some other server name".into(),
13066            LspSettings {
13067                binary: None,
13068                settings: None,
13069                initialization_options: Some(json!({
13070                    "some other init value": false
13071                })),
13072                enable_lsp_tasks: false,
13073            },
13074        );
13075    });
13076    cx.executor().run_until_parked();
13077    assert_eq!(
13078        server_restarts.load(atomic::Ordering::Acquire),
13079        0,
13080        "Should not restart LSP server on an unrelated LSP settings change"
13081    );
13082
13083    update_test_project_settings(cx, |project_settings| {
13084        project_settings.lsp.insert(
13085            language_server_name.into(),
13086            LspSettings {
13087                binary: None,
13088                settings: None,
13089                initialization_options: Some(json!({
13090                    "anotherInitValue": false
13091                })),
13092                enable_lsp_tasks: false,
13093            },
13094        );
13095    });
13096    cx.executor().run_until_parked();
13097    assert_eq!(
13098        server_restarts.load(atomic::Ordering::Acquire),
13099        1,
13100        "Should restart LSP server on a related LSP settings change"
13101    );
13102
13103    update_test_project_settings(cx, |project_settings| {
13104        project_settings.lsp.insert(
13105            language_server_name.into(),
13106            LspSettings {
13107                binary: None,
13108                settings: None,
13109                initialization_options: Some(json!({
13110                    "anotherInitValue": false
13111                })),
13112                enable_lsp_tasks: false,
13113            },
13114        );
13115    });
13116    cx.executor().run_until_parked();
13117    assert_eq!(
13118        server_restarts.load(atomic::Ordering::Acquire),
13119        1,
13120        "Should not restart LSP server on a related LSP settings change that is the same"
13121    );
13122
13123    update_test_project_settings(cx, |project_settings| {
13124        project_settings.lsp.insert(
13125            language_server_name.into(),
13126            LspSettings {
13127                binary: None,
13128                settings: None,
13129                initialization_options: None,
13130                enable_lsp_tasks: false,
13131            },
13132        );
13133    });
13134    cx.executor().run_until_parked();
13135    assert_eq!(
13136        server_restarts.load(atomic::Ordering::Acquire),
13137        2,
13138        "Should restart LSP server on another related LSP settings change"
13139    );
13140}
13141
13142#[gpui::test]
13143async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13144    init_test(cx, |_| {});
13145
13146    let mut cx = EditorLspTestContext::new_rust(
13147        lsp::ServerCapabilities {
13148            completion_provider: Some(lsp::CompletionOptions {
13149                trigger_characters: Some(vec![".".to_string()]),
13150                resolve_provider: Some(true),
13151                ..Default::default()
13152            }),
13153            ..Default::default()
13154        },
13155        cx,
13156    )
13157    .await;
13158
13159    cx.set_state("fn main() { let a = 2ˇ; }");
13160    cx.simulate_keystroke(".");
13161    let completion_item = lsp::CompletionItem {
13162        label: "some".into(),
13163        kind: Some(lsp::CompletionItemKind::SNIPPET),
13164        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13165        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13166            kind: lsp::MarkupKind::Markdown,
13167            value: "```rust\nSome(2)\n```".to_string(),
13168        })),
13169        deprecated: Some(false),
13170        sort_text: Some("fffffff2".to_string()),
13171        filter_text: Some("some".to_string()),
13172        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13173        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13174            range: lsp::Range {
13175                start: lsp::Position {
13176                    line: 0,
13177                    character: 22,
13178                },
13179                end: lsp::Position {
13180                    line: 0,
13181                    character: 22,
13182                },
13183            },
13184            new_text: "Some(2)".to_string(),
13185        })),
13186        additional_text_edits: Some(vec![lsp::TextEdit {
13187            range: lsp::Range {
13188                start: lsp::Position {
13189                    line: 0,
13190                    character: 20,
13191                },
13192                end: lsp::Position {
13193                    line: 0,
13194                    character: 22,
13195                },
13196            },
13197            new_text: "".to_string(),
13198        }]),
13199        ..Default::default()
13200    };
13201
13202    let closure_completion_item = completion_item.clone();
13203    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13204        let task_completion_item = closure_completion_item.clone();
13205        async move {
13206            Ok(Some(lsp::CompletionResponse::Array(vec![
13207                task_completion_item,
13208            ])))
13209        }
13210    });
13211
13212    request.next().await;
13213
13214    cx.condition(|editor, _| editor.context_menu_visible())
13215        .await;
13216    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13217        editor
13218            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13219            .unwrap()
13220    });
13221    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13222
13223    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13224        let task_completion_item = completion_item.clone();
13225        async move { Ok(task_completion_item) }
13226    })
13227    .next()
13228    .await
13229    .unwrap();
13230    apply_additional_edits.await.unwrap();
13231    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13232}
13233
13234#[gpui::test]
13235async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13236    init_test(cx, |_| {});
13237
13238    let mut cx = EditorLspTestContext::new_rust(
13239        lsp::ServerCapabilities {
13240            completion_provider: Some(lsp::CompletionOptions {
13241                trigger_characters: Some(vec![".".to_string()]),
13242                resolve_provider: Some(true),
13243                ..Default::default()
13244            }),
13245            ..Default::default()
13246        },
13247        cx,
13248    )
13249    .await;
13250
13251    cx.set_state("fn main() { let a = 2ˇ; }");
13252    cx.simulate_keystroke(".");
13253
13254    let item1 = lsp::CompletionItem {
13255        label: "method id()".to_string(),
13256        filter_text: Some("id".to_string()),
13257        detail: None,
13258        documentation: None,
13259        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13260            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13261            new_text: ".id".to_string(),
13262        })),
13263        ..lsp::CompletionItem::default()
13264    };
13265
13266    let item2 = lsp::CompletionItem {
13267        label: "other".to_string(),
13268        filter_text: Some("other".to_string()),
13269        detail: None,
13270        documentation: None,
13271        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13272            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13273            new_text: ".other".to_string(),
13274        })),
13275        ..lsp::CompletionItem::default()
13276    };
13277
13278    let item1 = item1.clone();
13279    cx.set_request_handler::<lsp::request::Completion, _, _>({
13280        let item1 = item1.clone();
13281        move |_, _, _| {
13282            let item1 = item1.clone();
13283            let item2 = item2.clone();
13284            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13285        }
13286    })
13287    .next()
13288    .await;
13289
13290    cx.condition(|editor, _| editor.context_menu_visible())
13291        .await;
13292    cx.update_editor(|editor, _, _| {
13293        let context_menu = editor.context_menu.borrow_mut();
13294        let context_menu = context_menu
13295            .as_ref()
13296            .expect("Should have the context menu deployed");
13297        match context_menu {
13298            CodeContextMenu::Completions(completions_menu) => {
13299                let completions = completions_menu.completions.borrow_mut();
13300                assert_eq!(
13301                    completions
13302                        .iter()
13303                        .map(|completion| &completion.label.text)
13304                        .collect::<Vec<_>>(),
13305                    vec!["method id()", "other"]
13306                )
13307            }
13308            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13309        }
13310    });
13311
13312    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13313        let item1 = item1.clone();
13314        move |_, item_to_resolve, _| {
13315            let item1 = item1.clone();
13316            async move {
13317                if item1 == item_to_resolve {
13318                    Ok(lsp::CompletionItem {
13319                        label: "method id()".to_string(),
13320                        filter_text: Some("id".to_string()),
13321                        detail: Some("Now resolved!".to_string()),
13322                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13323                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13324                            range: lsp::Range::new(
13325                                lsp::Position::new(0, 22),
13326                                lsp::Position::new(0, 22),
13327                            ),
13328                            new_text: ".id".to_string(),
13329                        })),
13330                        ..lsp::CompletionItem::default()
13331                    })
13332                } else {
13333                    Ok(item_to_resolve)
13334                }
13335            }
13336        }
13337    })
13338    .next()
13339    .await
13340    .unwrap();
13341    cx.run_until_parked();
13342
13343    cx.update_editor(|editor, window, cx| {
13344        editor.context_menu_next(&Default::default(), window, cx);
13345    });
13346
13347    cx.update_editor(|editor, _, _| {
13348        let context_menu = editor.context_menu.borrow_mut();
13349        let context_menu = context_menu
13350            .as_ref()
13351            .expect("Should have the context menu deployed");
13352        match context_menu {
13353            CodeContextMenu::Completions(completions_menu) => {
13354                let completions = completions_menu.completions.borrow_mut();
13355                assert_eq!(
13356                    completions
13357                        .iter()
13358                        .map(|completion| &completion.label.text)
13359                        .collect::<Vec<_>>(),
13360                    vec!["method id() Now resolved!", "other"],
13361                    "Should update first completion label, but not second as the filter text did not match."
13362                );
13363            }
13364            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13365        }
13366    });
13367}
13368
13369#[gpui::test]
13370async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13371    init_test(cx, |_| {});
13372
13373    let mut cx = EditorLspTestContext::new_rust(
13374        lsp::ServerCapabilities {
13375            completion_provider: Some(lsp::CompletionOptions {
13376                trigger_characters: Some(vec![".".to_string()]),
13377                resolve_provider: Some(true),
13378                ..Default::default()
13379            }),
13380            ..Default::default()
13381        },
13382        cx,
13383    )
13384    .await;
13385
13386    cx.set_state("fn main() { let a = 2ˇ; }");
13387    cx.simulate_keystroke(".");
13388
13389    let unresolved_item_1 = lsp::CompletionItem {
13390        label: "id".to_string(),
13391        filter_text: Some("id".to_string()),
13392        detail: None,
13393        documentation: None,
13394        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13395            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13396            new_text: ".id".to_string(),
13397        })),
13398        ..lsp::CompletionItem::default()
13399    };
13400    let resolved_item_1 = lsp::CompletionItem {
13401        additional_text_edits: Some(vec![lsp::TextEdit {
13402            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13403            new_text: "!!".to_string(),
13404        }]),
13405        ..unresolved_item_1.clone()
13406    };
13407    let unresolved_item_2 = lsp::CompletionItem {
13408        label: "other".to_string(),
13409        filter_text: Some("other".to_string()),
13410        detail: None,
13411        documentation: None,
13412        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13413            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13414            new_text: ".other".to_string(),
13415        })),
13416        ..lsp::CompletionItem::default()
13417    };
13418    let resolved_item_2 = lsp::CompletionItem {
13419        additional_text_edits: Some(vec![lsp::TextEdit {
13420            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13421            new_text: "??".to_string(),
13422        }]),
13423        ..unresolved_item_2.clone()
13424    };
13425
13426    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
13427    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
13428    cx.lsp
13429        .server
13430        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13431            let unresolved_item_1 = unresolved_item_1.clone();
13432            let resolved_item_1 = resolved_item_1.clone();
13433            let unresolved_item_2 = unresolved_item_2.clone();
13434            let resolved_item_2 = resolved_item_2.clone();
13435            let resolve_requests_1 = resolve_requests_1.clone();
13436            let resolve_requests_2 = resolve_requests_2.clone();
13437            move |unresolved_request, _| {
13438                let unresolved_item_1 = unresolved_item_1.clone();
13439                let resolved_item_1 = resolved_item_1.clone();
13440                let unresolved_item_2 = unresolved_item_2.clone();
13441                let resolved_item_2 = resolved_item_2.clone();
13442                let resolve_requests_1 = resolve_requests_1.clone();
13443                let resolve_requests_2 = resolve_requests_2.clone();
13444                async move {
13445                    if unresolved_request == unresolved_item_1 {
13446                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
13447                        Ok(resolved_item_1.clone())
13448                    } else if unresolved_request == unresolved_item_2 {
13449                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
13450                        Ok(resolved_item_2.clone())
13451                    } else {
13452                        panic!("Unexpected completion item {unresolved_request:?}")
13453                    }
13454                }
13455            }
13456        })
13457        .detach();
13458
13459    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13460        let unresolved_item_1 = unresolved_item_1.clone();
13461        let unresolved_item_2 = unresolved_item_2.clone();
13462        async move {
13463            Ok(Some(lsp::CompletionResponse::Array(vec![
13464                unresolved_item_1,
13465                unresolved_item_2,
13466            ])))
13467        }
13468    })
13469    .next()
13470    .await;
13471
13472    cx.condition(|editor, _| editor.context_menu_visible())
13473        .await;
13474    cx.update_editor(|editor, _, _| {
13475        let context_menu = editor.context_menu.borrow_mut();
13476        let context_menu = context_menu
13477            .as_ref()
13478            .expect("Should have the context menu deployed");
13479        match context_menu {
13480            CodeContextMenu::Completions(completions_menu) => {
13481                let completions = completions_menu.completions.borrow_mut();
13482                assert_eq!(
13483                    completions
13484                        .iter()
13485                        .map(|completion| &completion.label.text)
13486                        .collect::<Vec<_>>(),
13487                    vec!["id", "other"]
13488                )
13489            }
13490            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13491        }
13492    });
13493    cx.run_until_parked();
13494
13495    cx.update_editor(|editor, window, cx| {
13496        editor.context_menu_next(&ContextMenuNext, window, cx);
13497    });
13498    cx.run_until_parked();
13499    cx.update_editor(|editor, window, cx| {
13500        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13501    });
13502    cx.run_until_parked();
13503    cx.update_editor(|editor, window, cx| {
13504        editor.context_menu_next(&ContextMenuNext, window, cx);
13505    });
13506    cx.run_until_parked();
13507    cx.update_editor(|editor, window, cx| {
13508        editor
13509            .compose_completion(&ComposeCompletion::default(), window, cx)
13510            .expect("No task returned")
13511    })
13512    .await
13513    .expect("Completion failed");
13514    cx.run_until_parked();
13515
13516    cx.update_editor(|editor, _, cx| {
13517        assert_eq!(
13518            resolve_requests_1.load(atomic::Ordering::Acquire),
13519            1,
13520            "Should always resolve once despite multiple selections"
13521        );
13522        assert_eq!(
13523            resolve_requests_2.load(atomic::Ordering::Acquire),
13524            1,
13525            "Should always resolve once after multiple selections and applying the completion"
13526        );
13527        assert_eq!(
13528            editor.text(cx),
13529            "fn main() { let a = ??.other; }",
13530            "Should use resolved data when applying the completion"
13531        );
13532    });
13533}
13534
13535#[gpui::test]
13536async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
13537    init_test(cx, |_| {});
13538
13539    let item_0 = lsp::CompletionItem {
13540        label: "abs".into(),
13541        insert_text: Some("abs".into()),
13542        data: Some(json!({ "very": "special"})),
13543        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
13544        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13545            lsp::InsertReplaceEdit {
13546                new_text: "abs".to_string(),
13547                insert: lsp::Range::default(),
13548                replace: lsp::Range::default(),
13549            },
13550        )),
13551        ..lsp::CompletionItem::default()
13552    };
13553    let items = iter::once(item_0.clone())
13554        .chain((11..51).map(|i| lsp::CompletionItem {
13555            label: format!("item_{}", i),
13556            insert_text: Some(format!("item_{}", i)),
13557            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13558            ..lsp::CompletionItem::default()
13559        }))
13560        .collect::<Vec<_>>();
13561
13562    let default_commit_characters = vec!["?".to_string()];
13563    let default_data = json!({ "default": "data"});
13564    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
13565    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
13566    let default_edit_range = lsp::Range {
13567        start: lsp::Position {
13568            line: 0,
13569            character: 5,
13570        },
13571        end: lsp::Position {
13572            line: 0,
13573            character: 5,
13574        },
13575    };
13576
13577    let mut cx = EditorLspTestContext::new_rust(
13578        lsp::ServerCapabilities {
13579            completion_provider: Some(lsp::CompletionOptions {
13580                trigger_characters: Some(vec![".".to_string()]),
13581                resolve_provider: Some(true),
13582                ..Default::default()
13583            }),
13584            ..Default::default()
13585        },
13586        cx,
13587    )
13588    .await;
13589
13590    cx.set_state("fn main() { let a = 2ˇ; }");
13591    cx.simulate_keystroke(".");
13592
13593    let completion_data = default_data.clone();
13594    let completion_characters = default_commit_characters.clone();
13595    let completion_items = items.clone();
13596    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13597        let default_data = completion_data.clone();
13598        let default_commit_characters = completion_characters.clone();
13599        let items = completion_items.clone();
13600        async move {
13601            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13602                items,
13603                item_defaults: Some(lsp::CompletionListItemDefaults {
13604                    data: Some(default_data.clone()),
13605                    commit_characters: Some(default_commit_characters.clone()),
13606                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
13607                        default_edit_range,
13608                    )),
13609                    insert_text_format: Some(default_insert_text_format),
13610                    insert_text_mode: Some(default_insert_text_mode),
13611                }),
13612                ..lsp::CompletionList::default()
13613            })))
13614        }
13615    })
13616    .next()
13617    .await;
13618
13619    let resolved_items = Arc::new(Mutex::new(Vec::new()));
13620    cx.lsp
13621        .server
13622        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
13623            let closure_resolved_items = resolved_items.clone();
13624            move |item_to_resolve, _| {
13625                let closure_resolved_items = closure_resolved_items.clone();
13626                async move {
13627                    closure_resolved_items.lock().push(item_to_resolve.clone());
13628                    Ok(item_to_resolve)
13629                }
13630            }
13631        })
13632        .detach();
13633
13634    cx.condition(|editor, _| editor.context_menu_visible())
13635        .await;
13636    cx.run_until_parked();
13637    cx.update_editor(|editor, _, _| {
13638        let menu = editor.context_menu.borrow_mut();
13639        match menu.as_ref().expect("should have the completions menu") {
13640            CodeContextMenu::Completions(completions_menu) => {
13641                assert_eq!(
13642                    completions_menu
13643                        .entries
13644                        .borrow()
13645                        .iter()
13646                        .map(|mat| mat.string.clone())
13647                        .collect::<Vec<String>>(),
13648                    items
13649                        .iter()
13650                        .map(|completion| completion.label.clone())
13651                        .collect::<Vec<String>>()
13652                );
13653            }
13654            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
13655        }
13656    });
13657    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
13658    // with 4 from the end.
13659    assert_eq!(
13660        *resolved_items.lock(),
13661        [&items[0..16], &items[items.len() - 4..items.len()]]
13662            .concat()
13663            .iter()
13664            .cloned()
13665            .map(|mut item| {
13666                if item.data.is_none() {
13667                    item.data = Some(default_data.clone());
13668                }
13669                item
13670            })
13671            .collect::<Vec<lsp::CompletionItem>>(),
13672        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13673    );
13674    resolved_items.lock().clear();
13675
13676    cx.update_editor(|editor, window, cx| {
13677        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13678    });
13679    cx.run_until_parked();
13680    // Completions that have already been resolved are skipped.
13681    assert_eq!(
13682        *resolved_items.lock(),
13683        items[items.len() - 16..items.len() - 4]
13684            .iter()
13685            .cloned()
13686            .map(|mut item| {
13687                if item.data.is_none() {
13688                    item.data = Some(default_data.clone());
13689                }
13690                item
13691            })
13692            .collect::<Vec<lsp::CompletionItem>>()
13693    );
13694    resolved_items.lock().clear();
13695}
13696
13697#[gpui::test]
13698async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13699    init_test(cx, |_| {});
13700
13701    let mut cx = EditorLspTestContext::new(
13702        Language::new(
13703            LanguageConfig {
13704                matcher: LanguageMatcher {
13705                    path_suffixes: vec!["jsx".into()],
13706                    ..Default::default()
13707                },
13708                overrides: [(
13709                    "element".into(),
13710                    LanguageConfigOverride {
13711                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
13712                        ..Default::default()
13713                    },
13714                )]
13715                .into_iter()
13716                .collect(),
13717                ..Default::default()
13718            },
13719            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13720        )
13721        .with_override_query("(jsx_self_closing_element) @element")
13722        .unwrap(),
13723        lsp::ServerCapabilities {
13724            completion_provider: Some(lsp::CompletionOptions {
13725                trigger_characters: Some(vec![":".to_string()]),
13726                ..Default::default()
13727            }),
13728            ..Default::default()
13729        },
13730        cx,
13731    )
13732    .await;
13733
13734    cx.lsp
13735        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13736            Ok(Some(lsp::CompletionResponse::Array(vec![
13737                lsp::CompletionItem {
13738                    label: "bg-blue".into(),
13739                    ..Default::default()
13740                },
13741                lsp::CompletionItem {
13742                    label: "bg-red".into(),
13743                    ..Default::default()
13744                },
13745                lsp::CompletionItem {
13746                    label: "bg-yellow".into(),
13747                    ..Default::default()
13748                },
13749            ])))
13750        });
13751
13752    cx.set_state(r#"<p class="bgˇ" />"#);
13753
13754    // Trigger completion when typing a dash, because the dash is an extra
13755    // word character in the 'element' scope, which contains the cursor.
13756    cx.simulate_keystroke("-");
13757    cx.executor().run_until_parked();
13758    cx.update_editor(|editor, _, _| {
13759        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13760        {
13761            assert_eq!(
13762                completion_menu_entries(&menu),
13763                &["bg-red", "bg-blue", "bg-yellow"]
13764            );
13765        } else {
13766            panic!("expected completion menu to be open");
13767        }
13768    });
13769
13770    cx.simulate_keystroke("l");
13771    cx.executor().run_until_parked();
13772    cx.update_editor(|editor, _, _| {
13773        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13774        {
13775            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
13776        } else {
13777            panic!("expected completion menu to be open");
13778        }
13779    });
13780
13781    // When filtering completions, consider the character after the '-' to
13782    // be the start of a subword.
13783    cx.set_state(r#"<p class="yelˇ" />"#);
13784    cx.simulate_keystroke("l");
13785    cx.executor().run_until_parked();
13786    cx.update_editor(|editor, _, _| {
13787        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13788        {
13789            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
13790        } else {
13791            panic!("expected completion menu to be open");
13792        }
13793    });
13794}
13795
13796fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
13797    let entries = menu.entries.borrow();
13798    entries.iter().map(|mat| mat.string.clone()).collect()
13799}
13800
13801#[gpui::test]
13802async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
13803    init_test(cx, |settings| {
13804        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
13805            FormatterList(vec![Formatter::Prettier].into()),
13806        ))
13807    });
13808
13809    let fs = FakeFs::new(cx.executor());
13810    fs.insert_file(path!("/file.ts"), Default::default()).await;
13811
13812    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
13813    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13814
13815    language_registry.add(Arc::new(Language::new(
13816        LanguageConfig {
13817            name: "TypeScript".into(),
13818            matcher: LanguageMatcher {
13819                path_suffixes: vec!["ts".to_string()],
13820                ..Default::default()
13821            },
13822            ..Default::default()
13823        },
13824        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13825    )));
13826    update_test_language_settings(cx, |settings| {
13827        settings.defaults.prettier = Some(PrettierSettings {
13828            allowed: true,
13829            ..PrettierSettings::default()
13830        });
13831    });
13832
13833    let test_plugin = "test_plugin";
13834    let _ = language_registry.register_fake_lsp(
13835        "TypeScript",
13836        FakeLspAdapter {
13837            prettier_plugins: vec![test_plugin],
13838            ..Default::default()
13839        },
13840    );
13841
13842    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13843    let buffer = project
13844        .update(cx, |project, cx| {
13845            project.open_local_buffer(path!("/file.ts"), cx)
13846        })
13847        .await
13848        .unwrap();
13849
13850    let buffer_text = "one\ntwo\nthree\n";
13851    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13852    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13853    editor.update_in(cx, |editor, window, cx| {
13854        editor.set_text(buffer_text, window, cx)
13855    });
13856
13857    editor
13858        .update_in(cx, |editor, window, cx| {
13859            editor.perform_format(
13860                project.clone(),
13861                FormatTrigger::Manual,
13862                FormatTarget::Buffers,
13863                window,
13864                cx,
13865            )
13866        })
13867        .unwrap()
13868        .await;
13869    assert_eq!(
13870        editor.update(cx, |editor, cx| editor.text(cx)),
13871        buffer_text.to_string() + prettier_format_suffix,
13872        "Test prettier formatting was not applied to the original buffer text",
13873    );
13874
13875    update_test_language_settings(cx, |settings| {
13876        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13877    });
13878    let format = editor.update_in(cx, |editor, window, cx| {
13879        editor.perform_format(
13880            project.clone(),
13881            FormatTrigger::Manual,
13882            FormatTarget::Buffers,
13883            window,
13884            cx,
13885        )
13886    });
13887    format.await.unwrap();
13888    assert_eq!(
13889        editor.update(cx, |editor, cx| editor.text(cx)),
13890        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13891        "Autoformatting (via test prettier) was not applied to the original buffer text",
13892    );
13893}
13894
13895#[gpui::test]
13896async fn test_addition_reverts(cx: &mut TestAppContext) {
13897    init_test(cx, |_| {});
13898    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13899    let base_text = indoc! {r#"
13900        struct Row;
13901        struct Row1;
13902        struct Row2;
13903
13904        struct Row4;
13905        struct Row5;
13906        struct Row6;
13907
13908        struct Row8;
13909        struct Row9;
13910        struct Row10;"#};
13911
13912    // When addition hunks are not adjacent to carets, no hunk revert is performed
13913    assert_hunk_revert(
13914        indoc! {r#"struct Row;
13915                   struct Row1;
13916                   struct Row1.1;
13917                   struct Row1.2;
13918                   struct Row2;ˇ
13919
13920                   struct Row4;
13921                   struct Row5;
13922                   struct Row6;
13923
13924                   struct Row8;
13925                   ˇstruct Row9;
13926                   struct Row9.1;
13927                   struct Row9.2;
13928                   struct Row9.3;
13929                   struct Row10;"#},
13930        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13931        indoc! {r#"struct Row;
13932                   struct Row1;
13933                   struct Row1.1;
13934                   struct Row1.2;
13935                   struct Row2;ˇ
13936
13937                   struct Row4;
13938                   struct Row5;
13939                   struct Row6;
13940
13941                   struct Row8;
13942                   ˇstruct Row9;
13943                   struct Row9.1;
13944                   struct Row9.2;
13945                   struct Row9.3;
13946                   struct Row10;"#},
13947        base_text,
13948        &mut cx,
13949    );
13950    // Same for selections
13951    assert_hunk_revert(
13952        indoc! {r#"struct Row;
13953                   struct Row1;
13954                   struct Row2;
13955                   struct Row2.1;
13956                   struct Row2.2;
13957                   «ˇ
13958                   struct Row4;
13959                   struct» Row5;
13960                   «struct Row6;
13961                   ˇ»
13962                   struct Row9.1;
13963                   struct Row9.2;
13964                   struct Row9.3;
13965                   struct Row8;
13966                   struct Row9;
13967                   struct Row10;"#},
13968        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13969        indoc! {r#"struct Row;
13970                   struct Row1;
13971                   struct Row2;
13972                   struct Row2.1;
13973                   struct Row2.2;
13974                   «ˇ
13975                   struct Row4;
13976                   struct» Row5;
13977                   «struct Row6;
13978                   ˇ»
13979                   struct Row9.1;
13980                   struct Row9.2;
13981                   struct Row9.3;
13982                   struct Row8;
13983                   struct Row9;
13984                   struct Row10;"#},
13985        base_text,
13986        &mut cx,
13987    );
13988
13989    // When carets and selections intersect the addition hunks, those are reverted.
13990    // Adjacent carets got merged.
13991    assert_hunk_revert(
13992        indoc! {r#"struct Row;
13993                   ˇ// something on the top
13994                   struct Row1;
13995                   struct Row2;
13996                   struct Roˇw3.1;
13997                   struct Row2.2;
13998                   struct Row2.3;ˇ
13999
14000                   struct Row4;
14001                   struct ˇRow5.1;
14002                   struct Row5.2;
14003                   struct «Rowˇ»5.3;
14004                   struct Row5;
14005                   struct Row6;
14006                   ˇ
14007                   struct Row9.1;
14008                   struct «Rowˇ»9.2;
14009                   struct «ˇRow»9.3;
14010                   struct Row8;
14011                   struct Row9;
14012                   «ˇ// something on bottom»
14013                   struct Row10;"#},
14014        vec![
14015            DiffHunkStatusKind::Added,
14016            DiffHunkStatusKind::Added,
14017            DiffHunkStatusKind::Added,
14018            DiffHunkStatusKind::Added,
14019            DiffHunkStatusKind::Added,
14020        ],
14021        indoc! {r#"struct Row;
14022                   ˇstruct Row1;
14023                   struct Row2;
14024                   ˇ
14025                   struct Row4;
14026                   ˇstruct Row5;
14027                   struct Row6;
14028                   ˇ
14029                   ˇstruct Row8;
14030                   struct Row9;
14031                   ˇstruct Row10;"#},
14032        base_text,
14033        &mut cx,
14034    );
14035}
14036
14037#[gpui::test]
14038async fn test_modification_reverts(cx: &mut TestAppContext) {
14039    init_test(cx, |_| {});
14040    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14041    let base_text = indoc! {r#"
14042        struct Row;
14043        struct Row1;
14044        struct Row2;
14045
14046        struct Row4;
14047        struct Row5;
14048        struct Row6;
14049
14050        struct Row8;
14051        struct Row9;
14052        struct Row10;"#};
14053
14054    // Modification hunks behave the same as the addition ones.
14055    assert_hunk_revert(
14056        indoc! {r#"struct Row;
14057                   struct Row1;
14058                   struct Row33;
14059                   ˇ
14060                   struct Row4;
14061                   struct Row5;
14062                   struct Row6;
14063                   ˇ
14064                   struct Row99;
14065                   struct Row9;
14066                   struct Row10;"#},
14067        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14068        indoc! {r#"struct Row;
14069                   struct Row1;
14070                   struct Row33;
14071                   ˇ
14072                   struct Row4;
14073                   struct Row5;
14074                   struct Row6;
14075                   ˇ
14076                   struct Row99;
14077                   struct Row9;
14078                   struct Row10;"#},
14079        base_text,
14080        &mut cx,
14081    );
14082    assert_hunk_revert(
14083        indoc! {r#"struct Row;
14084                   struct Row1;
14085                   struct Row33;
14086                   «ˇ
14087                   struct Row4;
14088                   struct» Row5;
14089                   «struct Row6;
14090                   ˇ»
14091                   struct Row99;
14092                   struct Row9;
14093                   struct Row10;"#},
14094        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14095        indoc! {r#"struct Row;
14096                   struct Row1;
14097                   struct Row33;
14098                   «ˇ
14099                   struct Row4;
14100                   struct» Row5;
14101                   «struct Row6;
14102                   ˇ»
14103                   struct Row99;
14104                   struct Row9;
14105                   struct Row10;"#},
14106        base_text,
14107        &mut cx,
14108    );
14109
14110    assert_hunk_revert(
14111        indoc! {r#"ˇstruct Row1.1;
14112                   struct Row1;
14113                   «ˇstr»uct Row22;
14114
14115                   struct ˇRow44;
14116                   struct Row5;
14117                   struct «Rˇ»ow66;ˇ
14118
14119                   «struˇ»ct Row88;
14120                   struct Row9;
14121                   struct Row1011;ˇ"#},
14122        vec![
14123            DiffHunkStatusKind::Modified,
14124            DiffHunkStatusKind::Modified,
14125            DiffHunkStatusKind::Modified,
14126            DiffHunkStatusKind::Modified,
14127            DiffHunkStatusKind::Modified,
14128            DiffHunkStatusKind::Modified,
14129        ],
14130        indoc! {r#"struct Row;
14131                   ˇstruct Row1;
14132                   struct Row2;
14133                   ˇ
14134                   struct Row4;
14135                   ˇstruct Row5;
14136                   struct Row6;
14137                   ˇ
14138                   struct Row8;
14139                   ˇstruct Row9;
14140                   struct Row10;ˇ"#},
14141        base_text,
14142        &mut cx,
14143    );
14144}
14145
14146#[gpui::test]
14147async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14148    init_test(cx, |_| {});
14149    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14150    let base_text = indoc! {r#"
14151        one
14152
14153        two
14154        three
14155        "#};
14156
14157    cx.set_head_text(base_text);
14158    cx.set_state("\nˇ\n");
14159    cx.executor().run_until_parked();
14160    cx.update_editor(|editor, _window, cx| {
14161        editor.expand_selected_diff_hunks(cx);
14162    });
14163    cx.executor().run_until_parked();
14164    cx.update_editor(|editor, window, cx| {
14165        editor.backspace(&Default::default(), window, cx);
14166    });
14167    cx.run_until_parked();
14168    cx.assert_state_with_diff(
14169        indoc! {r#"
14170
14171        - two
14172        - threeˇ
14173        +
14174        "#}
14175        .to_string(),
14176    );
14177}
14178
14179#[gpui::test]
14180async fn test_deletion_reverts(cx: &mut TestAppContext) {
14181    init_test(cx, |_| {});
14182    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14183    let base_text = indoc! {r#"struct Row;
14184struct Row1;
14185struct Row2;
14186
14187struct Row4;
14188struct Row5;
14189struct Row6;
14190
14191struct Row8;
14192struct Row9;
14193struct Row10;"#};
14194
14195    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14196    assert_hunk_revert(
14197        indoc! {r#"struct Row;
14198                   struct Row2;
14199
14200                   ˇstruct Row4;
14201                   struct Row5;
14202                   struct Row6;
14203                   ˇ
14204                   struct Row8;
14205                   struct Row10;"#},
14206        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14207        indoc! {r#"struct Row;
14208                   struct Row2;
14209
14210                   ˇstruct Row4;
14211                   struct Row5;
14212                   struct Row6;
14213                   ˇ
14214                   struct Row8;
14215                   struct Row10;"#},
14216        base_text,
14217        &mut cx,
14218    );
14219    assert_hunk_revert(
14220        indoc! {r#"struct Row;
14221                   struct Row2;
14222
14223                   «ˇstruct Row4;
14224                   struct» Row5;
14225                   «struct Row6;
14226                   ˇ»
14227                   struct Row8;
14228                   struct Row10;"#},
14229        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14230        indoc! {r#"struct Row;
14231                   struct Row2;
14232
14233                   «ˇstruct Row4;
14234                   struct» Row5;
14235                   «struct Row6;
14236                   ˇ»
14237                   struct Row8;
14238                   struct Row10;"#},
14239        base_text,
14240        &mut cx,
14241    );
14242
14243    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14244    assert_hunk_revert(
14245        indoc! {r#"struct Row;
14246                   ˇstruct Row2;
14247
14248                   struct Row4;
14249                   struct Row5;
14250                   struct Row6;
14251
14252                   struct Row8;ˇ
14253                   struct Row10;"#},
14254        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14255        indoc! {r#"struct Row;
14256                   struct Row1;
14257                   ˇstruct Row2;
14258
14259                   struct Row4;
14260                   struct Row5;
14261                   struct Row6;
14262
14263                   struct Row8;ˇ
14264                   struct Row9;
14265                   struct Row10;"#},
14266        base_text,
14267        &mut cx,
14268    );
14269    assert_hunk_revert(
14270        indoc! {r#"struct Row;
14271                   struct Row2«ˇ;
14272                   struct Row4;
14273                   struct» Row5;
14274                   «struct Row6;
14275
14276                   struct Row8;ˇ»
14277                   struct Row10;"#},
14278        vec![
14279            DiffHunkStatusKind::Deleted,
14280            DiffHunkStatusKind::Deleted,
14281            DiffHunkStatusKind::Deleted,
14282        ],
14283        indoc! {r#"struct Row;
14284                   struct Row1;
14285                   struct Row2«ˇ;
14286
14287                   struct Row4;
14288                   struct» Row5;
14289                   «struct Row6;
14290
14291                   struct Row8;ˇ»
14292                   struct Row9;
14293                   struct Row10;"#},
14294        base_text,
14295        &mut cx,
14296    );
14297}
14298
14299#[gpui::test]
14300async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14301    init_test(cx, |_| {});
14302
14303    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14304    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14305    let base_text_3 =
14306        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14307
14308    let text_1 = edit_first_char_of_every_line(base_text_1);
14309    let text_2 = edit_first_char_of_every_line(base_text_2);
14310    let text_3 = edit_first_char_of_every_line(base_text_3);
14311
14312    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14313    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14314    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14315
14316    let multibuffer = cx.new(|cx| {
14317        let mut multibuffer = MultiBuffer::new(ReadWrite);
14318        multibuffer.push_excerpts(
14319            buffer_1.clone(),
14320            [
14321                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14322                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14323                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14324            ],
14325            cx,
14326        );
14327        multibuffer.push_excerpts(
14328            buffer_2.clone(),
14329            [
14330                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14331                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14332                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14333            ],
14334            cx,
14335        );
14336        multibuffer.push_excerpts(
14337            buffer_3.clone(),
14338            [
14339                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14340                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14341                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14342            ],
14343            cx,
14344        );
14345        multibuffer
14346    });
14347
14348    let fs = FakeFs::new(cx.executor());
14349    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14350    let (editor, cx) = cx
14351        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14352    editor.update_in(cx, |editor, _window, cx| {
14353        for (buffer, diff_base) in [
14354            (buffer_1.clone(), base_text_1),
14355            (buffer_2.clone(), base_text_2),
14356            (buffer_3.clone(), base_text_3),
14357        ] {
14358            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14359            editor
14360                .buffer
14361                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14362        }
14363    });
14364    cx.executor().run_until_parked();
14365
14366    editor.update_in(cx, |editor, window, cx| {
14367        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}");
14368        editor.select_all(&SelectAll, window, cx);
14369        editor.git_restore(&Default::default(), window, cx);
14370    });
14371    cx.executor().run_until_parked();
14372
14373    // When all ranges are selected, all buffer hunks are reverted.
14374    editor.update(cx, |editor, cx| {
14375        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");
14376    });
14377    buffer_1.update(cx, |buffer, _| {
14378        assert_eq!(buffer.text(), base_text_1);
14379    });
14380    buffer_2.update(cx, |buffer, _| {
14381        assert_eq!(buffer.text(), base_text_2);
14382    });
14383    buffer_3.update(cx, |buffer, _| {
14384        assert_eq!(buffer.text(), base_text_3);
14385    });
14386
14387    editor.update_in(cx, |editor, window, cx| {
14388        editor.undo(&Default::default(), window, cx);
14389    });
14390
14391    editor.update_in(cx, |editor, window, cx| {
14392        editor.change_selections(None, window, cx, |s| {
14393            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14394        });
14395        editor.git_restore(&Default::default(), window, cx);
14396    });
14397
14398    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14399    // but not affect buffer_2 and its related excerpts.
14400    editor.update(cx, |editor, cx| {
14401        assert_eq!(
14402            editor.text(cx),
14403            "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}"
14404        );
14405    });
14406    buffer_1.update(cx, |buffer, _| {
14407        assert_eq!(buffer.text(), base_text_1);
14408    });
14409    buffer_2.update(cx, |buffer, _| {
14410        assert_eq!(
14411            buffer.text(),
14412            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
14413        );
14414    });
14415    buffer_3.update(cx, |buffer, _| {
14416        assert_eq!(
14417            buffer.text(),
14418            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
14419        );
14420    });
14421
14422    fn edit_first_char_of_every_line(text: &str) -> String {
14423        text.split('\n')
14424            .map(|line| format!("X{}", &line[1..]))
14425            .collect::<Vec<_>>()
14426            .join("\n")
14427    }
14428}
14429
14430#[gpui::test]
14431async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
14432    init_test(cx, |_| {});
14433
14434    let cols = 4;
14435    let rows = 10;
14436    let sample_text_1 = sample_text(rows, cols, 'a');
14437    assert_eq!(
14438        sample_text_1,
14439        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
14440    );
14441    let sample_text_2 = sample_text(rows, cols, 'l');
14442    assert_eq!(
14443        sample_text_2,
14444        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
14445    );
14446    let sample_text_3 = sample_text(rows, cols, 'v');
14447    assert_eq!(
14448        sample_text_3,
14449        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
14450    );
14451
14452    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
14453    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
14454    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
14455
14456    let multi_buffer = cx.new(|cx| {
14457        let mut multibuffer = MultiBuffer::new(ReadWrite);
14458        multibuffer.push_excerpts(
14459            buffer_1.clone(),
14460            [
14461                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14462                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14463                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14464            ],
14465            cx,
14466        );
14467        multibuffer.push_excerpts(
14468            buffer_2.clone(),
14469            [
14470                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14471                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14472                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14473            ],
14474            cx,
14475        );
14476        multibuffer.push_excerpts(
14477            buffer_3.clone(),
14478            [
14479                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14480                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14481                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14482            ],
14483            cx,
14484        );
14485        multibuffer
14486    });
14487
14488    let fs = FakeFs::new(cx.executor());
14489    fs.insert_tree(
14490        "/a",
14491        json!({
14492            "main.rs": sample_text_1,
14493            "other.rs": sample_text_2,
14494            "lib.rs": sample_text_3,
14495        }),
14496    )
14497    .await;
14498    let project = Project::test(fs, ["/a".as_ref()], cx).await;
14499    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14500    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14501    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14502        Editor::new(
14503            EditorMode::Full,
14504            multi_buffer,
14505            Some(project.clone()),
14506            window,
14507            cx,
14508        )
14509    });
14510    let multibuffer_item_id = workspace
14511        .update(cx, |workspace, window, cx| {
14512            assert!(
14513                workspace.active_item(cx).is_none(),
14514                "active item should be None before the first item is added"
14515            );
14516            workspace.add_item_to_active_pane(
14517                Box::new(multi_buffer_editor.clone()),
14518                None,
14519                true,
14520                window,
14521                cx,
14522            );
14523            let active_item = workspace
14524                .active_item(cx)
14525                .expect("should have an active item after adding the multi buffer");
14526            assert!(
14527                !active_item.is_singleton(cx),
14528                "A multi buffer was expected to active after adding"
14529            );
14530            active_item.item_id()
14531        })
14532        .unwrap();
14533    cx.executor().run_until_parked();
14534
14535    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14536        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14537            s.select_ranges(Some(1..2))
14538        });
14539        editor.open_excerpts(&OpenExcerpts, window, cx);
14540    });
14541    cx.executor().run_until_parked();
14542    let first_item_id = workspace
14543        .update(cx, |workspace, window, cx| {
14544            let active_item = workspace
14545                .active_item(cx)
14546                .expect("should have an active item after navigating into the 1st buffer");
14547            let first_item_id = active_item.item_id();
14548            assert_ne!(
14549                first_item_id, multibuffer_item_id,
14550                "Should navigate into the 1st buffer and activate it"
14551            );
14552            assert!(
14553                active_item.is_singleton(cx),
14554                "New active item should be a singleton buffer"
14555            );
14556            assert_eq!(
14557                active_item
14558                    .act_as::<Editor>(cx)
14559                    .expect("should have navigated into an editor for the 1st buffer")
14560                    .read(cx)
14561                    .text(cx),
14562                sample_text_1
14563            );
14564
14565            workspace
14566                .go_back(workspace.active_pane().downgrade(), window, cx)
14567                .detach_and_log_err(cx);
14568
14569            first_item_id
14570        })
14571        .unwrap();
14572    cx.executor().run_until_parked();
14573    workspace
14574        .update(cx, |workspace, _, cx| {
14575            let active_item = workspace
14576                .active_item(cx)
14577                .expect("should have an active item after navigating back");
14578            assert_eq!(
14579                active_item.item_id(),
14580                multibuffer_item_id,
14581                "Should navigate back to the multi buffer"
14582            );
14583            assert!(!active_item.is_singleton(cx));
14584        })
14585        .unwrap();
14586
14587    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14588        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14589            s.select_ranges(Some(39..40))
14590        });
14591        editor.open_excerpts(&OpenExcerpts, window, cx);
14592    });
14593    cx.executor().run_until_parked();
14594    let second_item_id = workspace
14595        .update(cx, |workspace, window, cx| {
14596            let active_item = workspace
14597                .active_item(cx)
14598                .expect("should have an active item after navigating into the 2nd buffer");
14599            let second_item_id = active_item.item_id();
14600            assert_ne!(
14601                second_item_id, multibuffer_item_id,
14602                "Should navigate away from the multibuffer"
14603            );
14604            assert_ne!(
14605                second_item_id, first_item_id,
14606                "Should navigate into the 2nd buffer and activate it"
14607            );
14608            assert!(
14609                active_item.is_singleton(cx),
14610                "New active item should be a singleton buffer"
14611            );
14612            assert_eq!(
14613                active_item
14614                    .act_as::<Editor>(cx)
14615                    .expect("should have navigated into an editor")
14616                    .read(cx)
14617                    .text(cx),
14618                sample_text_2
14619            );
14620
14621            workspace
14622                .go_back(workspace.active_pane().downgrade(), window, cx)
14623                .detach_and_log_err(cx);
14624
14625            second_item_id
14626        })
14627        .unwrap();
14628    cx.executor().run_until_parked();
14629    workspace
14630        .update(cx, |workspace, _, cx| {
14631            let active_item = workspace
14632                .active_item(cx)
14633                .expect("should have an active item after navigating back from the 2nd buffer");
14634            assert_eq!(
14635                active_item.item_id(),
14636                multibuffer_item_id,
14637                "Should navigate back from the 2nd buffer to the multi buffer"
14638            );
14639            assert!(!active_item.is_singleton(cx));
14640        })
14641        .unwrap();
14642
14643    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14644        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14645            s.select_ranges(Some(70..70))
14646        });
14647        editor.open_excerpts(&OpenExcerpts, window, cx);
14648    });
14649    cx.executor().run_until_parked();
14650    workspace
14651        .update(cx, |workspace, window, cx| {
14652            let active_item = workspace
14653                .active_item(cx)
14654                .expect("should have an active item after navigating into the 3rd buffer");
14655            let third_item_id = active_item.item_id();
14656            assert_ne!(
14657                third_item_id, multibuffer_item_id,
14658                "Should navigate into the 3rd buffer and activate it"
14659            );
14660            assert_ne!(third_item_id, first_item_id);
14661            assert_ne!(third_item_id, second_item_id);
14662            assert!(
14663                active_item.is_singleton(cx),
14664                "New active item should be a singleton buffer"
14665            );
14666            assert_eq!(
14667                active_item
14668                    .act_as::<Editor>(cx)
14669                    .expect("should have navigated into an editor")
14670                    .read(cx)
14671                    .text(cx),
14672                sample_text_3
14673            );
14674
14675            workspace
14676                .go_back(workspace.active_pane().downgrade(), window, cx)
14677                .detach_and_log_err(cx);
14678        })
14679        .unwrap();
14680    cx.executor().run_until_parked();
14681    workspace
14682        .update(cx, |workspace, _, cx| {
14683            let active_item = workspace
14684                .active_item(cx)
14685                .expect("should have an active item after navigating back from the 3rd buffer");
14686            assert_eq!(
14687                active_item.item_id(),
14688                multibuffer_item_id,
14689                "Should navigate back from the 3rd buffer to the multi buffer"
14690            );
14691            assert!(!active_item.is_singleton(cx));
14692        })
14693        .unwrap();
14694}
14695
14696#[gpui::test]
14697async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14698    init_test(cx, |_| {});
14699
14700    let mut cx = EditorTestContext::new(cx).await;
14701
14702    let diff_base = r#"
14703        use some::mod;
14704
14705        const A: u32 = 42;
14706
14707        fn main() {
14708            println!("hello");
14709
14710            println!("world");
14711        }
14712        "#
14713    .unindent();
14714
14715    cx.set_state(
14716        &r#"
14717        use some::modified;
14718
14719        ˇ
14720        fn main() {
14721            println!("hello there");
14722
14723            println!("around the");
14724            println!("world");
14725        }
14726        "#
14727        .unindent(),
14728    );
14729
14730    cx.set_head_text(&diff_base);
14731    executor.run_until_parked();
14732
14733    cx.update_editor(|editor, window, cx| {
14734        editor.go_to_next_hunk(&GoToHunk, window, cx);
14735        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14736    });
14737    executor.run_until_parked();
14738    cx.assert_state_with_diff(
14739        r#"
14740          use some::modified;
14741
14742
14743          fn main() {
14744        -     println!("hello");
14745        + ˇ    println!("hello there");
14746
14747              println!("around the");
14748              println!("world");
14749          }
14750        "#
14751        .unindent(),
14752    );
14753
14754    cx.update_editor(|editor, window, cx| {
14755        for _ in 0..2 {
14756            editor.go_to_next_hunk(&GoToHunk, window, cx);
14757            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14758        }
14759    });
14760    executor.run_until_parked();
14761    cx.assert_state_with_diff(
14762        r#"
14763        - use some::mod;
14764        + ˇuse some::modified;
14765
14766
14767          fn main() {
14768        -     println!("hello");
14769        +     println!("hello there");
14770
14771        +     println!("around the");
14772              println!("world");
14773          }
14774        "#
14775        .unindent(),
14776    );
14777
14778    cx.update_editor(|editor, window, cx| {
14779        editor.go_to_next_hunk(&GoToHunk, window, cx);
14780        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14781    });
14782    executor.run_until_parked();
14783    cx.assert_state_with_diff(
14784        r#"
14785        - use some::mod;
14786        + use some::modified;
14787
14788        - const A: u32 = 42;
14789          ˇ
14790          fn main() {
14791        -     println!("hello");
14792        +     println!("hello there");
14793
14794        +     println!("around the");
14795              println!("world");
14796          }
14797        "#
14798        .unindent(),
14799    );
14800
14801    cx.update_editor(|editor, window, cx| {
14802        editor.cancel(&Cancel, window, cx);
14803    });
14804
14805    cx.assert_state_with_diff(
14806        r#"
14807          use some::modified;
14808
14809          ˇ
14810          fn main() {
14811              println!("hello there");
14812
14813              println!("around the");
14814              println!("world");
14815          }
14816        "#
14817        .unindent(),
14818    );
14819}
14820
14821#[gpui::test]
14822async fn test_diff_base_change_with_expanded_diff_hunks(
14823    executor: BackgroundExecutor,
14824    cx: &mut TestAppContext,
14825) {
14826    init_test(cx, |_| {});
14827
14828    let mut cx = EditorTestContext::new(cx).await;
14829
14830    let diff_base = r#"
14831        use some::mod1;
14832        use some::mod2;
14833
14834        const A: u32 = 42;
14835        const B: u32 = 42;
14836        const C: u32 = 42;
14837
14838        fn main() {
14839            println!("hello");
14840
14841            println!("world");
14842        }
14843        "#
14844    .unindent();
14845
14846    cx.set_state(
14847        &r#"
14848        use some::mod2;
14849
14850        const A: u32 = 42;
14851        const C: u32 = 42;
14852
14853        fn main(ˇ) {
14854            //println!("hello");
14855
14856            println!("world");
14857            //
14858            //
14859        }
14860        "#
14861        .unindent(),
14862    );
14863
14864    cx.set_head_text(&diff_base);
14865    executor.run_until_parked();
14866
14867    cx.update_editor(|editor, window, cx| {
14868        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14869    });
14870    executor.run_until_parked();
14871    cx.assert_state_with_diff(
14872        r#"
14873        - use some::mod1;
14874          use some::mod2;
14875
14876          const A: u32 = 42;
14877        - const B: u32 = 42;
14878          const C: u32 = 42;
14879
14880          fn main(ˇ) {
14881        -     println!("hello");
14882        +     //println!("hello");
14883
14884              println!("world");
14885        +     //
14886        +     //
14887          }
14888        "#
14889        .unindent(),
14890    );
14891
14892    cx.set_head_text("new diff base!");
14893    executor.run_until_parked();
14894    cx.assert_state_with_diff(
14895        r#"
14896        - new diff base!
14897        + use some::mod2;
14898        +
14899        + const A: u32 = 42;
14900        + const C: u32 = 42;
14901        +
14902        + fn main(ˇ) {
14903        +     //println!("hello");
14904        +
14905        +     println!("world");
14906        +     //
14907        +     //
14908        + }
14909        "#
14910        .unindent(),
14911    );
14912}
14913
14914#[gpui::test]
14915async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14916    init_test(cx, |_| {});
14917
14918    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14919    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14920    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14921    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14922    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14923    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14924
14925    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14926    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14927    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14928
14929    let multi_buffer = cx.new(|cx| {
14930        let mut multibuffer = MultiBuffer::new(ReadWrite);
14931        multibuffer.push_excerpts(
14932            buffer_1.clone(),
14933            [
14934                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14935                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14936                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14937            ],
14938            cx,
14939        );
14940        multibuffer.push_excerpts(
14941            buffer_2.clone(),
14942            [
14943                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14944                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14945                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14946            ],
14947            cx,
14948        );
14949        multibuffer.push_excerpts(
14950            buffer_3.clone(),
14951            [
14952                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14953                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14954                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
14955            ],
14956            cx,
14957        );
14958        multibuffer
14959    });
14960
14961    let editor =
14962        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14963    editor
14964        .update(cx, |editor, _window, cx| {
14965            for (buffer, diff_base) in [
14966                (buffer_1.clone(), file_1_old),
14967                (buffer_2.clone(), file_2_old),
14968                (buffer_3.clone(), file_3_old),
14969            ] {
14970                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14971                editor
14972                    .buffer
14973                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14974            }
14975        })
14976        .unwrap();
14977
14978    let mut cx = EditorTestContext::for_editor(editor, cx).await;
14979    cx.run_until_parked();
14980
14981    cx.assert_editor_state(
14982        &"
14983            ˇaaa
14984            ccc
14985            ddd
14986
14987            ggg
14988            hhh
14989
14990
14991            lll
14992            mmm
14993            NNN
14994
14995            qqq
14996            rrr
14997
14998            uuu
14999            111
15000            222
15001            333
15002
15003            666
15004            777
15005
15006            000
15007            !!!"
15008        .unindent(),
15009    );
15010
15011    cx.update_editor(|editor, window, cx| {
15012        editor.select_all(&SelectAll, window, cx);
15013        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15014    });
15015    cx.executor().run_until_parked();
15016
15017    cx.assert_state_with_diff(
15018        "
15019            «aaa
15020          - bbb
15021            ccc
15022            ddd
15023
15024            ggg
15025            hhh
15026
15027
15028            lll
15029            mmm
15030          - nnn
15031          + NNN
15032
15033            qqq
15034            rrr
15035
15036            uuu
15037            111
15038            222
15039            333
15040
15041          + 666
15042            777
15043
15044            000
15045            !!!ˇ»"
15046            .unindent(),
15047    );
15048}
15049
15050#[gpui::test]
15051async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15052    init_test(cx, |_| {});
15053
15054    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15055    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15056
15057    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15058    let multi_buffer = cx.new(|cx| {
15059        let mut multibuffer = MultiBuffer::new(ReadWrite);
15060        multibuffer.push_excerpts(
15061            buffer.clone(),
15062            [
15063                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15064                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15065                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15066            ],
15067            cx,
15068        );
15069        multibuffer
15070    });
15071
15072    let editor =
15073        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
15074    editor
15075        .update(cx, |editor, _window, cx| {
15076            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15077            editor
15078                .buffer
15079                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15080        })
15081        .unwrap();
15082
15083    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15084    cx.run_until_parked();
15085
15086    cx.update_editor(|editor, window, cx| {
15087        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15088    });
15089    cx.executor().run_until_parked();
15090
15091    // When the start of a hunk coincides with the start of its excerpt,
15092    // the hunk is expanded. When the start of a a hunk is earlier than
15093    // the start of its excerpt, the hunk is not expanded.
15094    cx.assert_state_with_diff(
15095        "
15096            ˇaaa
15097          - bbb
15098          + BBB
15099
15100          - ddd
15101          - eee
15102          + DDD
15103          + EEE
15104            fff
15105
15106            iii
15107        "
15108        .unindent(),
15109    );
15110}
15111
15112#[gpui::test]
15113async fn test_edits_around_expanded_insertion_hunks(
15114    executor: BackgroundExecutor,
15115    cx: &mut TestAppContext,
15116) {
15117    init_test(cx, |_| {});
15118
15119    let mut cx = EditorTestContext::new(cx).await;
15120
15121    let diff_base = r#"
15122        use some::mod1;
15123        use some::mod2;
15124
15125        const A: u32 = 42;
15126
15127        fn main() {
15128            println!("hello");
15129
15130            println!("world");
15131        }
15132        "#
15133    .unindent();
15134    executor.run_until_parked();
15135    cx.set_state(
15136        &r#"
15137        use some::mod1;
15138        use some::mod2;
15139
15140        const A: u32 = 42;
15141        const B: u32 = 42;
15142        const C: u32 = 42;
15143        ˇ
15144
15145        fn main() {
15146            println!("hello");
15147
15148            println!("world");
15149        }
15150        "#
15151        .unindent(),
15152    );
15153
15154    cx.set_head_text(&diff_base);
15155    executor.run_until_parked();
15156
15157    cx.update_editor(|editor, window, cx| {
15158        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15159    });
15160    executor.run_until_parked();
15161
15162    cx.assert_state_with_diff(
15163        r#"
15164        use some::mod1;
15165        use some::mod2;
15166
15167        const A: u32 = 42;
15168      + const B: u32 = 42;
15169      + const C: u32 = 42;
15170      + ˇ
15171
15172        fn main() {
15173            println!("hello");
15174
15175            println!("world");
15176        }
15177      "#
15178        .unindent(),
15179    );
15180
15181    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15182    executor.run_until_parked();
15183
15184    cx.assert_state_with_diff(
15185        r#"
15186        use some::mod1;
15187        use some::mod2;
15188
15189        const A: u32 = 42;
15190      + const B: u32 = 42;
15191      + const C: u32 = 42;
15192      + const D: u32 = 42;
15193      + ˇ
15194
15195        fn main() {
15196            println!("hello");
15197
15198            println!("world");
15199        }
15200      "#
15201        .unindent(),
15202    );
15203
15204    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15205    executor.run_until_parked();
15206
15207    cx.assert_state_with_diff(
15208        r#"
15209        use some::mod1;
15210        use some::mod2;
15211
15212        const A: u32 = 42;
15213      + const B: u32 = 42;
15214      + const C: u32 = 42;
15215      + const D: u32 = 42;
15216      + const E: u32 = 42;
15217      + ˇ
15218
15219        fn main() {
15220            println!("hello");
15221
15222            println!("world");
15223        }
15224      "#
15225        .unindent(),
15226    );
15227
15228    cx.update_editor(|editor, window, cx| {
15229        editor.delete_line(&DeleteLine, window, cx);
15230    });
15231    executor.run_until_parked();
15232
15233    cx.assert_state_with_diff(
15234        r#"
15235        use some::mod1;
15236        use some::mod2;
15237
15238        const A: u32 = 42;
15239      + const B: u32 = 42;
15240      + const C: u32 = 42;
15241      + const D: u32 = 42;
15242      + const E: u32 = 42;
15243        ˇ
15244        fn main() {
15245            println!("hello");
15246
15247            println!("world");
15248        }
15249      "#
15250        .unindent(),
15251    );
15252
15253    cx.update_editor(|editor, window, cx| {
15254        editor.move_up(&MoveUp, window, cx);
15255        editor.delete_line(&DeleteLine, window, cx);
15256        editor.move_up(&MoveUp, window, cx);
15257        editor.delete_line(&DeleteLine, window, cx);
15258        editor.move_up(&MoveUp, window, cx);
15259        editor.delete_line(&DeleteLine, window, cx);
15260    });
15261    executor.run_until_parked();
15262    cx.assert_state_with_diff(
15263        r#"
15264        use some::mod1;
15265        use some::mod2;
15266
15267        const A: u32 = 42;
15268      + const B: u32 = 42;
15269        ˇ
15270        fn main() {
15271            println!("hello");
15272
15273            println!("world");
15274        }
15275      "#
15276        .unindent(),
15277    );
15278
15279    cx.update_editor(|editor, window, cx| {
15280        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15281        editor.delete_line(&DeleteLine, window, cx);
15282    });
15283    executor.run_until_parked();
15284    cx.assert_state_with_diff(
15285        r#"
15286        ˇ
15287        fn main() {
15288            println!("hello");
15289
15290            println!("world");
15291        }
15292      "#
15293        .unindent(),
15294    );
15295}
15296
15297#[gpui::test]
15298async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15299    init_test(cx, |_| {});
15300
15301    let mut cx = EditorTestContext::new(cx).await;
15302    cx.set_head_text(indoc! { "
15303        one
15304        two
15305        three
15306        four
15307        five
15308        "
15309    });
15310    cx.set_state(indoc! { "
15311        one
15312        ˇthree
15313        five
15314    "});
15315    cx.run_until_parked();
15316    cx.update_editor(|editor, window, cx| {
15317        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15318    });
15319    cx.assert_state_with_diff(
15320        indoc! { "
15321        one
15322      - two
15323        ˇthree
15324      - four
15325        five
15326    "}
15327        .to_string(),
15328    );
15329    cx.update_editor(|editor, window, cx| {
15330        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15331    });
15332
15333    cx.assert_state_with_diff(
15334        indoc! { "
15335        one
15336        ˇthree
15337        five
15338    "}
15339        .to_string(),
15340    );
15341
15342    cx.set_state(indoc! { "
15343        one
15344        ˇTWO
15345        three
15346        four
15347        five
15348    "});
15349    cx.run_until_parked();
15350    cx.update_editor(|editor, window, cx| {
15351        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15352    });
15353
15354    cx.assert_state_with_diff(
15355        indoc! { "
15356            one
15357          - two
15358          + ˇTWO
15359            three
15360            four
15361            five
15362        "}
15363        .to_string(),
15364    );
15365    cx.update_editor(|editor, window, cx| {
15366        editor.move_up(&Default::default(), window, cx);
15367        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15368    });
15369    cx.assert_state_with_diff(
15370        indoc! { "
15371            one
15372            ˇTWO
15373            three
15374            four
15375            five
15376        "}
15377        .to_string(),
15378    );
15379}
15380
15381#[gpui::test]
15382async fn test_edits_around_expanded_deletion_hunks(
15383    executor: BackgroundExecutor,
15384    cx: &mut TestAppContext,
15385) {
15386    init_test(cx, |_| {});
15387
15388    let mut cx = EditorTestContext::new(cx).await;
15389
15390    let diff_base = r#"
15391        use some::mod1;
15392        use some::mod2;
15393
15394        const A: u32 = 42;
15395        const B: u32 = 42;
15396        const C: u32 = 42;
15397
15398
15399        fn main() {
15400            println!("hello");
15401
15402            println!("world");
15403        }
15404    "#
15405    .unindent();
15406    executor.run_until_parked();
15407    cx.set_state(
15408        &r#"
15409        use some::mod1;
15410        use some::mod2;
15411
15412        ˇconst B: u32 = 42;
15413        const C: u32 = 42;
15414
15415
15416        fn main() {
15417            println!("hello");
15418
15419            println!("world");
15420        }
15421        "#
15422        .unindent(),
15423    );
15424
15425    cx.set_head_text(&diff_base);
15426    executor.run_until_parked();
15427
15428    cx.update_editor(|editor, window, cx| {
15429        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15430    });
15431    executor.run_until_parked();
15432
15433    cx.assert_state_with_diff(
15434        r#"
15435        use some::mod1;
15436        use some::mod2;
15437
15438      - const A: u32 = 42;
15439        ˇconst B: u32 = 42;
15440        const C: u32 = 42;
15441
15442
15443        fn main() {
15444            println!("hello");
15445
15446            println!("world");
15447        }
15448      "#
15449        .unindent(),
15450    );
15451
15452    cx.update_editor(|editor, window, cx| {
15453        editor.delete_line(&DeleteLine, window, cx);
15454    });
15455    executor.run_until_parked();
15456    cx.assert_state_with_diff(
15457        r#"
15458        use some::mod1;
15459        use some::mod2;
15460
15461      - const A: u32 = 42;
15462      - const B: u32 = 42;
15463        ˇconst C: u32 = 42;
15464
15465
15466        fn main() {
15467            println!("hello");
15468
15469            println!("world");
15470        }
15471      "#
15472        .unindent(),
15473    );
15474
15475    cx.update_editor(|editor, window, cx| {
15476        editor.delete_line(&DeleteLine, window, cx);
15477    });
15478    executor.run_until_parked();
15479    cx.assert_state_with_diff(
15480        r#"
15481        use some::mod1;
15482        use some::mod2;
15483
15484      - const A: u32 = 42;
15485      - const B: u32 = 42;
15486      - const C: u32 = 42;
15487        ˇ
15488
15489        fn main() {
15490            println!("hello");
15491
15492            println!("world");
15493        }
15494      "#
15495        .unindent(),
15496    );
15497
15498    cx.update_editor(|editor, window, cx| {
15499        editor.handle_input("replacement", window, cx);
15500    });
15501    executor.run_until_parked();
15502    cx.assert_state_with_diff(
15503        r#"
15504        use some::mod1;
15505        use some::mod2;
15506
15507      - const A: u32 = 42;
15508      - const B: u32 = 42;
15509      - const C: u32 = 42;
15510      -
15511      + replacementˇ
15512
15513        fn main() {
15514            println!("hello");
15515
15516            println!("world");
15517        }
15518      "#
15519        .unindent(),
15520    );
15521}
15522
15523#[gpui::test]
15524async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15525    init_test(cx, |_| {});
15526
15527    let mut cx = EditorTestContext::new(cx).await;
15528
15529    let base_text = r#"
15530        one
15531        two
15532        three
15533        four
15534        five
15535    "#
15536    .unindent();
15537    executor.run_until_parked();
15538    cx.set_state(
15539        &r#"
15540        one
15541        two
15542        fˇour
15543        five
15544        "#
15545        .unindent(),
15546    );
15547
15548    cx.set_head_text(&base_text);
15549    executor.run_until_parked();
15550
15551    cx.update_editor(|editor, window, cx| {
15552        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15553    });
15554    executor.run_until_parked();
15555
15556    cx.assert_state_with_diff(
15557        r#"
15558          one
15559          two
15560        - three
15561          fˇour
15562          five
15563        "#
15564        .unindent(),
15565    );
15566
15567    cx.update_editor(|editor, window, cx| {
15568        editor.backspace(&Backspace, window, cx);
15569        editor.backspace(&Backspace, window, cx);
15570    });
15571    executor.run_until_parked();
15572    cx.assert_state_with_diff(
15573        r#"
15574          one
15575          two
15576        - threeˇ
15577        - four
15578        + our
15579          five
15580        "#
15581        .unindent(),
15582    );
15583}
15584
15585#[gpui::test]
15586async fn test_edit_after_expanded_modification_hunk(
15587    executor: BackgroundExecutor,
15588    cx: &mut TestAppContext,
15589) {
15590    init_test(cx, |_| {});
15591
15592    let mut cx = EditorTestContext::new(cx).await;
15593
15594    let diff_base = r#"
15595        use some::mod1;
15596        use some::mod2;
15597
15598        const A: u32 = 42;
15599        const B: u32 = 42;
15600        const C: u32 = 42;
15601        const D: u32 = 42;
15602
15603
15604        fn main() {
15605            println!("hello");
15606
15607            println!("world");
15608        }"#
15609    .unindent();
15610
15611    cx.set_state(
15612        &r#"
15613        use some::mod1;
15614        use some::mod2;
15615
15616        const A: u32 = 42;
15617        const B: u32 = 42;
15618        const C: u32 = 43ˇ
15619        const D: u32 = 42;
15620
15621
15622        fn main() {
15623            println!("hello");
15624
15625            println!("world");
15626        }"#
15627        .unindent(),
15628    );
15629
15630    cx.set_head_text(&diff_base);
15631    executor.run_until_parked();
15632    cx.update_editor(|editor, window, cx| {
15633        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15634    });
15635    executor.run_until_parked();
15636
15637    cx.assert_state_with_diff(
15638        r#"
15639        use some::mod1;
15640        use some::mod2;
15641
15642        const A: u32 = 42;
15643        const B: u32 = 42;
15644      - const C: u32 = 42;
15645      + const C: u32 = 43ˇ
15646        const D: u32 = 42;
15647
15648
15649        fn main() {
15650            println!("hello");
15651
15652            println!("world");
15653        }"#
15654        .unindent(),
15655    );
15656
15657    cx.update_editor(|editor, window, cx| {
15658        editor.handle_input("\nnew_line\n", window, cx);
15659    });
15660    executor.run_until_parked();
15661
15662    cx.assert_state_with_diff(
15663        r#"
15664        use some::mod1;
15665        use some::mod2;
15666
15667        const A: u32 = 42;
15668        const B: u32 = 42;
15669      - const C: u32 = 42;
15670      + const C: u32 = 43
15671      + new_line
15672      + ˇ
15673        const D: u32 = 42;
15674
15675
15676        fn main() {
15677            println!("hello");
15678
15679            println!("world");
15680        }"#
15681        .unindent(),
15682    );
15683}
15684
15685#[gpui::test]
15686async fn test_stage_and_unstage_added_file_hunk(
15687    executor: BackgroundExecutor,
15688    cx: &mut TestAppContext,
15689) {
15690    init_test(cx, |_| {});
15691
15692    let mut cx = EditorTestContext::new(cx).await;
15693    cx.update_editor(|editor, _, cx| {
15694        editor.set_expand_all_diff_hunks(cx);
15695    });
15696
15697    let working_copy = r#"
15698            ˇfn main() {
15699                println!("hello, world!");
15700            }
15701        "#
15702    .unindent();
15703
15704    cx.set_state(&working_copy);
15705    executor.run_until_parked();
15706
15707    cx.assert_state_with_diff(
15708        r#"
15709            + ˇfn main() {
15710            +     println!("hello, world!");
15711            + }
15712        "#
15713        .unindent(),
15714    );
15715    cx.assert_index_text(None);
15716
15717    cx.update_editor(|editor, window, cx| {
15718        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15719    });
15720    executor.run_until_parked();
15721    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15722    cx.assert_state_with_diff(
15723        r#"
15724            + ˇfn main() {
15725            +     println!("hello, world!");
15726            + }
15727        "#
15728        .unindent(),
15729    );
15730
15731    cx.update_editor(|editor, window, cx| {
15732        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15733    });
15734    executor.run_until_parked();
15735    cx.assert_index_text(None);
15736}
15737
15738async fn setup_indent_guides_editor(
15739    text: &str,
15740    cx: &mut TestAppContext,
15741) -> (BufferId, EditorTestContext) {
15742    init_test(cx, |_| {});
15743
15744    let mut cx = EditorTestContext::new(cx).await;
15745
15746    let buffer_id = cx.update_editor(|editor, window, cx| {
15747        editor.set_text(text, window, cx);
15748        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15749
15750        buffer_ids[0]
15751    });
15752
15753    (buffer_id, cx)
15754}
15755
15756fn assert_indent_guides(
15757    range: Range<u32>,
15758    expected: Vec<IndentGuide>,
15759    active_indices: Option<Vec<usize>>,
15760    cx: &mut EditorTestContext,
15761) {
15762    let indent_guides = cx.update_editor(|editor, window, cx| {
15763        let snapshot = editor.snapshot(window, cx).display_snapshot;
15764        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15765            editor,
15766            MultiBufferRow(range.start)..MultiBufferRow(range.end),
15767            true,
15768            &snapshot,
15769            cx,
15770        );
15771
15772        indent_guides.sort_by(|a, b| {
15773            a.depth.cmp(&b.depth).then(
15774                a.start_row
15775                    .cmp(&b.start_row)
15776                    .then(a.end_row.cmp(&b.end_row)),
15777            )
15778        });
15779        indent_guides
15780    });
15781
15782    if let Some(expected) = active_indices {
15783        let active_indices = cx.update_editor(|editor, window, cx| {
15784            let snapshot = editor.snapshot(window, cx).display_snapshot;
15785            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15786        });
15787
15788        assert_eq!(
15789            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15790            expected,
15791            "Active indent guide indices do not match"
15792        );
15793    }
15794
15795    assert_eq!(indent_guides, expected, "Indent guides do not match");
15796}
15797
15798fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15799    IndentGuide {
15800        buffer_id,
15801        start_row: MultiBufferRow(start_row),
15802        end_row: MultiBufferRow(end_row),
15803        depth,
15804        tab_size: 4,
15805        settings: IndentGuideSettings {
15806            enabled: true,
15807            line_width: 1,
15808            active_line_width: 1,
15809            ..Default::default()
15810        },
15811    }
15812}
15813
15814#[gpui::test]
15815async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15816    let (buffer_id, mut cx) = setup_indent_guides_editor(
15817        &"
15818    fn main() {
15819        let a = 1;
15820    }"
15821        .unindent(),
15822        cx,
15823    )
15824    .await;
15825
15826    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15827}
15828
15829#[gpui::test]
15830async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15831    let (buffer_id, mut cx) = setup_indent_guides_editor(
15832        &"
15833    fn main() {
15834        let a = 1;
15835        let b = 2;
15836    }"
15837        .unindent(),
15838        cx,
15839    )
15840    .await;
15841
15842    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15843}
15844
15845#[gpui::test]
15846async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15847    let (buffer_id, mut cx) = setup_indent_guides_editor(
15848        &"
15849    fn main() {
15850        let a = 1;
15851        if a == 3 {
15852            let b = 2;
15853        } else {
15854            let c = 3;
15855        }
15856    }"
15857        .unindent(),
15858        cx,
15859    )
15860    .await;
15861
15862    assert_indent_guides(
15863        0..8,
15864        vec![
15865            indent_guide(buffer_id, 1, 6, 0),
15866            indent_guide(buffer_id, 3, 3, 1),
15867            indent_guide(buffer_id, 5, 5, 1),
15868        ],
15869        None,
15870        &mut cx,
15871    );
15872}
15873
15874#[gpui::test]
15875async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15876    let (buffer_id, mut cx) = setup_indent_guides_editor(
15877        &"
15878    fn main() {
15879        let a = 1;
15880            let b = 2;
15881        let c = 3;
15882    }"
15883        .unindent(),
15884        cx,
15885    )
15886    .await;
15887
15888    assert_indent_guides(
15889        0..5,
15890        vec![
15891            indent_guide(buffer_id, 1, 3, 0),
15892            indent_guide(buffer_id, 2, 2, 1),
15893        ],
15894        None,
15895        &mut cx,
15896    );
15897}
15898
15899#[gpui::test]
15900async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15901    let (buffer_id, mut cx) = setup_indent_guides_editor(
15902        &"
15903        fn main() {
15904            let a = 1;
15905
15906            let c = 3;
15907        }"
15908        .unindent(),
15909        cx,
15910    )
15911    .await;
15912
15913    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15914}
15915
15916#[gpui::test]
15917async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15918    let (buffer_id, mut cx) = setup_indent_guides_editor(
15919        &"
15920        fn main() {
15921            let a = 1;
15922
15923            let c = 3;
15924
15925            if a == 3 {
15926                let b = 2;
15927            } else {
15928                let c = 3;
15929            }
15930        }"
15931        .unindent(),
15932        cx,
15933    )
15934    .await;
15935
15936    assert_indent_guides(
15937        0..11,
15938        vec![
15939            indent_guide(buffer_id, 1, 9, 0),
15940            indent_guide(buffer_id, 6, 6, 1),
15941            indent_guide(buffer_id, 8, 8, 1),
15942        ],
15943        None,
15944        &mut cx,
15945    );
15946}
15947
15948#[gpui::test]
15949async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15950    let (buffer_id, mut cx) = setup_indent_guides_editor(
15951        &"
15952        fn main() {
15953            let a = 1;
15954
15955            let c = 3;
15956
15957            if a == 3 {
15958                let b = 2;
15959            } else {
15960                let c = 3;
15961            }
15962        }"
15963        .unindent(),
15964        cx,
15965    )
15966    .await;
15967
15968    assert_indent_guides(
15969        1..11,
15970        vec![
15971            indent_guide(buffer_id, 1, 9, 0),
15972            indent_guide(buffer_id, 6, 6, 1),
15973            indent_guide(buffer_id, 8, 8, 1),
15974        ],
15975        None,
15976        &mut cx,
15977    );
15978}
15979
15980#[gpui::test]
15981async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15982    let (buffer_id, mut cx) = setup_indent_guides_editor(
15983        &"
15984        fn main() {
15985            let a = 1;
15986
15987            let c = 3;
15988
15989            if a == 3 {
15990                let b = 2;
15991            } else {
15992                let c = 3;
15993            }
15994        }"
15995        .unindent(),
15996        cx,
15997    )
15998    .await;
15999
16000    assert_indent_guides(
16001        1..10,
16002        vec![
16003            indent_guide(buffer_id, 1, 9, 0),
16004            indent_guide(buffer_id, 6, 6, 1),
16005            indent_guide(buffer_id, 8, 8, 1),
16006        ],
16007        None,
16008        &mut cx,
16009    );
16010}
16011
16012#[gpui::test]
16013async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16014    let (buffer_id, mut cx) = setup_indent_guides_editor(
16015        &"
16016        block1
16017            block2
16018                block3
16019                    block4
16020            block2
16021        block1
16022        block1"
16023            .unindent(),
16024        cx,
16025    )
16026    .await;
16027
16028    assert_indent_guides(
16029        1..10,
16030        vec![
16031            indent_guide(buffer_id, 1, 4, 0),
16032            indent_guide(buffer_id, 2, 3, 1),
16033            indent_guide(buffer_id, 3, 3, 2),
16034        ],
16035        None,
16036        &mut cx,
16037    );
16038}
16039
16040#[gpui::test]
16041async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16042    let (buffer_id, mut cx) = setup_indent_guides_editor(
16043        &"
16044        block1
16045            block2
16046                block3
16047
16048        block1
16049        block1"
16050            .unindent(),
16051        cx,
16052    )
16053    .await;
16054
16055    assert_indent_guides(
16056        0..6,
16057        vec![
16058            indent_guide(buffer_id, 1, 2, 0),
16059            indent_guide(buffer_id, 2, 2, 1),
16060        ],
16061        None,
16062        &mut cx,
16063    );
16064}
16065
16066#[gpui::test]
16067async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16068    let (buffer_id, mut cx) = setup_indent_guides_editor(
16069        &"
16070        block1
16071
16072
16073
16074            block2
16075        "
16076        .unindent(),
16077        cx,
16078    )
16079    .await;
16080
16081    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16082}
16083
16084#[gpui::test]
16085async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16086    let (buffer_id, mut cx) = setup_indent_guides_editor(
16087        &"
16088        def a:
16089        \tb = 3
16090        \tif True:
16091        \t\tc = 4
16092        \t\td = 5
16093        \tprint(b)
16094        "
16095        .unindent(),
16096        cx,
16097    )
16098    .await;
16099
16100    assert_indent_guides(
16101        0..6,
16102        vec![
16103            indent_guide(buffer_id, 1, 6, 0),
16104            indent_guide(buffer_id, 3, 4, 1),
16105        ],
16106        None,
16107        &mut cx,
16108    );
16109}
16110
16111#[gpui::test]
16112async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16113    let (buffer_id, mut cx) = setup_indent_guides_editor(
16114        &"
16115    fn main() {
16116        let a = 1;
16117    }"
16118        .unindent(),
16119        cx,
16120    )
16121    .await;
16122
16123    cx.update_editor(|editor, window, cx| {
16124        editor.change_selections(None, window, cx, |s| {
16125            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16126        });
16127    });
16128
16129    assert_indent_guides(
16130        0..3,
16131        vec![indent_guide(buffer_id, 1, 1, 0)],
16132        Some(vec![0]),
16133        &mut cx,
16134    );
16135}
16136
16137#[gpui::test]
16138async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16139    let (buffer_id, mut cx) = setup_indent_guides_editor(
16140        &"
16141    fn main() {
16142        if 1 == 2 {
16143            let a = 1;
16144        }
16145    }"
16146        .unindent(),
16147        cx,
16148    )
16149    .await;
16150
16151    cx.update_editor(|editor, window, cx| {
16152        editor.change_selections(None, window, cx, |s| {
16153            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16154        });
16155    });
16156
16157    assert_indent_guides(
16158        0..4,
16159        vec![
16160            indent_guide(buffer_id, 1, 3, 0),
16161            indent_guide(buffer_id, 2, 2, 1),
16162        ],
16163        Some(vec![1]),
16164        &mut cx,
16165    );
16166
16167    cx.update_editor(|editor, window, cx| {
16168        editor.change_selections(None, window, cx, |s| {
16169            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16170        });
16171    });
16172
16173    assert_indent_guides(
16174        0..4,
16175        vec![
16176            indent_guide(buffer_id, 1, 3, 0),
16177            indent_guide(buffer_id, 2, 2, 1),
16178        ],
16179        Some(vec![1]),
16180        &mut cx,
16181    );
16182
16183    cx.update_editor(|editor, window, cx| {
16184        editor.change_selections(None, window, cx, |s| {
16185            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16186        });
16187    });
16188
16189    assert_indent_guides(
16190        0..4,
16191        vec![
16192            indent_guide(buffer_id, 1, 3, 0),
16193            indent_guide(buffer_id, 2, 2, 1),
16194        ],
16195        Some(vec![0]),
16196        &mut cx,
16197    );
16198}
16199
16200#[gpui::test]
16201async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16202    let (buffer_id, mut cx) = setup_indent_guides_editor(
16203        &"
16204    fn main() {
16205        let a = 1;
16206
16207        let b = 2;
16208    }"
16209        .unindent(),
16210        cx,
16211    )
16212    .await;
16213
16214    cx.update_editor(|editor, window, cx| {
16215        editor.change_selections(None, window, cx, |s| {
16216            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16217        });
16218    });
16219
16220    assert_indent_guides(
16221        0..5,
16222        vec![indent_guide(buffer_id, 1, 3, 0)],
16223        Some(vec![0]),
16224        &mut cx,
16225    );
16226}
16227
16228#[gpui::test]
16229async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16230    let (buffer_id, mut cx) = setup_indent_guides_editor(
16231        &"
16232    def m:
16233        a = 1
16234        pass"
16235            .unindent(),
16236        cx,
16237    )
16238    .await;
16239
16240    cx.update_editor(|editor, window, cx| {
16241        editor.change_selections(None, window, cx, |s| {
16242            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16243        });
16244    });
16245
16246    assert_indent_guides(
16247        0..3,
16248        vec![indent_guide(buffer_id, 1, 2, 0)],
16249        Some(vec![0]),
16250        &mut cx,
16251    );
16252}
16253
16254#[gpui::test]
16255async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16256    init_test(cx, |_| {});
16257    let mut cx = EditorTestContext::new(cx).await;
16258    let text = indoc! {
16259        "
16260        impl A {
16261            fn b() {
16262                0;
16263                3;
16264                5;
16265                6;
16266                7;
16267            }
16268        }
16269        "
16270    };
16271    let base_text = indoc! {
16272        "
16273        impl A {
16274            fn b() {
16275                0;
16276                1;
16277                2;
16278                3;
16279                4;
16280            }
16281            fn c() {
16282                5;
16283                6;
16284                7;
16285            }
16286        }
16287        "
16288    };
16289
16290    cx.update_editor(|editor, window, cx| {
16291        editor.set_text(text, window, cx);
16292
16293        editor.buffer().update(cx, |multibuffer, cx| {
16294            let buffer = multibuffer.as_singleton().unwrap();
16295            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16296
16297            multibuffer.set_all_diff_hunks_expanded(cx);
16298            multibuffer.add_diff(diff, cx);
16299
16300            buffer.read(cx).remote_id()
16301        })
16302    });
16303    cx.run_until_parked();
16304
16305    cx.assert_state_with_diff(
16306        indoc! { "
16307          impl A {
16308              fn b() {
16309                  0;
16310        -         1;
16311        -         2;
16312                  3;
16313        -         4;
16314        -     }
16315        -     fn c() {
16316                  5;
16317                  6;
16318                  7;
16319              }
16320          }
16321          ˇ"
16322        }
16323        .to_string(),
16324    );
16325
16326    let mut actual_guides = cx.update_editor(|editor, window, cx| {
16327        editor
16328            .snapshot(window, cx)
16329            .buffer_snapshot
16330            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16331            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16332            .collect::<Vec<_>>()
16333    });
16334    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16335    assert_eq!(
16336        actual_guides,
16337        vec![
16338            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16339            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16340            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16341        ]
16342    );
16343}
16344
16345#[gpui::test]
16346async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16347    init_test(cx, |_| {});
16348    let mut cx = EditorTestContext::new(cx).await;
16349
16350    let diff_base = r#"
16351        a
16352        b
16353        c
16354        "#
16355    .unindent();
16356
16357    cx.set_state(
16358        &r#"
16359        ˇA
16360        b
16361        C
16362        "#
16363        .unindent(),
16364    );
16365    cx.set_head_text(&diff_base);
16366    cx.update_editor(|editor, window, cx| {
16367        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16368    });
16369    executor.run_until_parked();
16370
16371    let both_hunks_expanded = r#"
16372        - a
16373        + ˇA
16374          b
16375        - c
16376        + C
16377        "#
16378    .unindent();
16379
16380    cx.assert_state_with_diff(both_hunks_expanded.clone());
16381
16382    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16383        let snapshot = editor.snapshot(window, cx);
16384        let hunks = editor
16385            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16386            .collect::<Vec<_>>();
16387        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16388        let buffer_id = hunks[0].buffer_id;
16389        hunks
16390            .into_iter()
16391            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16392            .collect::<Vec<_>>()
16393    });
16394    assert_eq!(hunk_ranges.len(), 2);
16395
16396    cx.update_editor(|editor, _, cx| {
16397        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16398    });
16399    executor.run_until_parked();
16400
16401    let second_hunk_expanded = r#"
16402          ˇA
16403          b
16404        - c
16405        + C
16406        "#
16407    .unindent();
16408
16409    cx.assert_state_with_diff(second_hunk_expanded);
16410
16411    cx.update_editor(|editor, _, cx| {
16412        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16413    });
16414    executor.run_until_parked();
16415
16416    cx.assert_state_with_diff(both_hunks_expanded.clone());
16417
16418    cx.update_editor(|editor, _, cx| {
16419        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16420    });
16421    executor.run_until_parked();
16422
16423    let first_hunk_expanded = r#"
16424        - a
16425        + ˇA
16426          b
16427          C
16428        "#
16429    .unindent();
16430
16431    cx.assert_state_with_diff(first_hunk_expanded);
16432
16433    cx.update_editor(|editor, _, cx| {
16434        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16435    });
16436    executor.run_until_parked();
16437
16438    cx.assert_state_with_diff(both_hunks_expanded);
16439
16440    cx.set_state(
16441        &r#"
16442        ˇA
16443        b
16444        "#
16445        .unindent(),
16446    );
16447    cx.run_until_parked();
16448
16449    // TODO this cursor position seems bad
16450    cx.assert_state_with_diff(
16451        r#"
16452        - ˇa
16453        + A
16454          b
16455        "#
16456        .unindent(),
16457    );
16458
16459    cx.update_editor(|editor, window, cx| {
16460        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16461    });
16462
16463    cx.assert_state_with_diff(
16464        r#"
16465            - ˇa
16466            + A
16467              b
16468            - c
16469            "#
16470        .unindent(),
16471    );
16472
16473    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16474        let snapshot = editor.snapshot(window, cx);
16475        let hunks = editor
16476            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16477            .collect::<Vec<_>>();
16478        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16479        let buffer_id = hunks[0].buffer_id;
16480        hunks
16481            .into_iter()
16482            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16483            .collect::<Vec<_>>()
16484    });
16485    assert_eq!(hunk_ranges.len(), 2);
16486
16487    cx.update_editor(|editor, _, cx| {
16488        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
16489    });
16490    executor.run_until_parked();
16491
16492    cx.assert_state_with_diff(
16493        r#"
16494        - ˇa
16495        + A
16496          b
16497        "#
16498        .unindent(),
16499    );
16500}
16501
16502#[gpui::test]
16503async fn test_toggle_deletion_hunk_at_start_of_file(
16504    executor: BackgroundExecutor,
16505    cx: &mut TestAppContext,
16506) {
16507    init_test(cx, |_| {});
16508    let mut cx = EditorTestContext::new(cx).await;
16509
16510    let diff_base = r#"
16511        a
16512        b
16513        c
16514        "#
16515    .unindent();
16516
16517    cx.set_state(
16518        &r#"
16519        ˇb
16520        c
16521        "#
16522        .unindent(),
16523    );
16524    cx.set_head_text(&diff_base);
16525    cx.update_editor(|editor, window, cx| {
16526        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16527    });
16528    executor.run_until_parked();
16529
16530    let hunk_expanded = r#"
16531        - a
16532          ˇb
16533          c
16534        "#
16535    .unindent();
16536
16537    cx.assert_state_with_diff(hunk_expanded.clone());
16538
16539    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16540        let snapshot = editor.snapshot(window, cx);
16541        let hunks = editor
16542            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16543            .collect::<Vec<_>>();
16544        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16545        let buffer_id = hunks[0].buffer_id;
16546        hunks
16547            .into_iter()
16548            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16549            .collect::<Vec<_>>()
16550    });
16551    assert_eq!(hunk_ranges.len(), 1);
16552
16553    cx.update_editor(|editor, _, cx| {
16554        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16555    });
16556    executor.run_until_parked();
16557
16558    let hunk_collapsed = r#"
16559          ˇb
16560          c
16561        "#
16562    .unindent();
16563
16564    cx.assert_state_with_diff(hunk_collapsed);
16565
16566    cx.update_editor(|editor, _, cx| {
16567        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16568    });
16569    executor.run_until_parked();
16570
16571    cx.assert_state_with_diff(hunk_expanded.clone());
16572}
16573
16574#[gpui::test]
16575async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16576    init_test(cx, |_| {});
16577
16578    let fs = FakeFs::new(cx.executor());
16579    fs.insert_tree(
16580        path!("/test"),
16581        json!({
16582            ".git": {},
16583            "file-1": "ONE\n",
16584            "file-2": "TWO\n",
16585            "file-3": "THREE\n",
16586        }),
16587    )
16588    .await;
16589
16590    fs.set_head_for_repo(
16591        path!("/test/.git").as_ref(),
16592        &[
16593            ("file-1".into(), "one\n".into()),
16594            ("file-2".into(), "two\n".into()),
16595            ("file-3".into(), "three\n".into()),
16596        ],
16597    );
16598
16599    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16600    let mut buffers = vec![];
16601    for i in 1..=3 {
16602        let buffer = project
16603            .update(cx, |project, cx| {
16604                let path = format!(path!("/test/file-{}"), i);
16605                project.open_local_buffer(path, cx)
16606            })
16607            .await
16608            .unwrap();
16609        buffers.push(buffer);
16610    }
16611
16612    let multibuffer = cx.new(|cx| {
16613        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16614        multibuffer.set_all_diff_hunks_expanded(cx);
16615        for buffer in &buffers {
16616            let snapshot = buffer.read(cx).snapshot();
16617            multibuffer.set_excerpts_for_path(
16618                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
16619                buffer.clone(),
16620                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16621                DEFAULT_MULTIBUFFER_CONTEXT,
16622                cx,
16623            );
16624        }
16625        multibuffer
16626    });
16627
16628    let editor = cx.add_window(|window, cx| {
16629        Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
16630    });
16631    cx.run_until_parked();
16632
16633    let snapshot = editor
16634        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16635        .unwrap();
16636    let hunks = snapshot
16637        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16638        .map(|hunk| match hunk {
16639            DisplayDiffHunk::Unfolded {
16640                display_row_range, ..
16641            } => display_row_range,
16642            DisplayDiffHunk::Folded { .. } => unreachable!(),
16643        })
16644        .collect::<Vec<_>>();
16645    assert_eq!(
16646        hunks,
16647        [
16648            DisplayRow(2)..DisplayRow(4),
16649            DisplayRow(7)..DisplayRow(9),
16650            DisplayRow(12)..DisplayRow(14),
16651        ]
16652    );
16653}
16654
16655#[gpui::test]
16656async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16657    init_test(cx, |_| {});
16658
16659    let mut cx = EditorTestContext::new(cx).await;
16660    cx.set_head_text(indoc! { "
16661        one
16662        two
16663        three
16664        four
16665        five
16666        "
16667    });
16668    cx.set_index_text(indoc! { "
16669        one
16670        two
16671        three
16672        four
16673        five
16674        "
16675    });
16676    cx.set_state(indoc! {"
16677        one
16678        TWO
16679        ˇTHREE
16680        FOUR
16681        five
16682    "});
16683    cx.run_until_parked();
16684    cx.update_editor(|editor, window, cx| {
16685        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16686    });
16687    cx.run_until_parked();
16688    cx.assert_index_text(Some(indoc! {"
16689        one
16690        TWO
16691        THREE
16692        FOUR
16693        five
16694    "}));
16695    cx.set_state(indoc! { "
16696        one
16697        TWO
16698        ˇTHREE-HUNDRED
16699        FOUR
16700        five
16701    "});
16702    cx.run_until_parked();
16703    cx.update_editor(|editor, window, cx| {
16704        let snapshot = editor.snapshot(window, cx);
16705        let hunks = editor
16706            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16707            .collect::<Vec<_>>();
16708        assert_eq!(hunks.len(), 1);
16709        assert_eq!(
16710            hunks[0].status(),
16711            DiffHunkStatus {
16712                kind: DiffHunkStatusKind::Modified,
16713                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16714            }
16715        );
16716
16717        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16718    });
16719    cx.run_until_parked();
16720    cx.assert_index_text(Some(indoc! {"
16721        one
16722        TWO
16723        THREE-HUNDRED
16724        FOUR
16725        five
16726    "}));
16727}
16728
16729#[gpui::test]
16730fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16731    init_test(cx, |_| {});
16732
16733    let editor = cx.add_window(|window, cx| {
16734        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16735        build_editor(buffer, window, cx)
16736    });
16737
16738    let render_args = Arc::new(Mutex::new(None));
16739    let snapshot = editor
16740        .update(cx, |editor, window, cx| {
16741            let snapshot = editor.buffer().read(cx).snapshot(cx);
16742            let range =
16743                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16744
16745            struct RenderArgs {
16746                row: MultiBufferRow,
16747                folded: bool,
16748                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16749            }
16750
16751            let crease = Crease::inline(
16752                range,
16753                FoldPlaceholder::test(),
16754                {
16755                    let toggle_callback = render_args.clone();
16756                    move |row, folded, callback, _window, _cx| {
16757                        *toggle_callback.lock() = Some(RenderArgs {
16758                            row,
16759                            folded,
16760                            callback,
16761                        });
16762                        div()
16763                    }
16764                },
16765                |_row, _folded, _window, _cx| div(),
16766            );
16767
16768            editor.insert_creases(Some(crease), cx);
16769            let snapshot = editor.snapshot(window, cx);
16770            let _div = snapshot.render_crease_toggle(
16771                MultiBufferRow(1),
16772                false,
16773                cx.entity().clone(),
16774                window,
16775                cx,
16776            );
16777            snapshot
16778        })
16779        .unwrap();
16780
16781    let render_args = render_args.lock().take().unwrap();
16782    assert_eq!(render_args.row, MultiBufferRow(1));
16783    assert!(!render_args.folded);
16784    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16785
16786    cx.update_window(*editor, |_, window, cx| {
16787        (render_args.callback)(true, window, cx)
16788    })
16789    .unwrap();
16790    let snapshot = editor
16791        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16792        .unwrap();
16793    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16794
16795    cx.update_window(*editor, |_, window, cx| {
16796        (render_args.callback)(false, window, cx)
16797    })
16798    .unwrap();
16799    let snapshot = editor
16800        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16801        .unwrap();
16802    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16803}
16804
16805#[gpui::test]
16806async fn test_input_text(cx: &mut TestAppContext) {
16807    init_test(cx, |_| {});
16808    let mut cx = EditorTestContext::new(cx).await;
16809
16810    cx.set_state(
16811        &r#"ˇone
16812        two
16813
16814        three
16815        fourˇ
16816        five
16817
16818        siˇx"#
16819            .unindent(),
16820    );
16821
16822    cx.dispatch_action(HandleInput(String::new()));
16823    cx.assert_editor_state(
16824        &r#"ˇone
16825        two
16826
16827        three
16828        fourˇ
16829        five
16830
16831        siˇx"#
16832            .unindent(),
16833    );
16834
16835    cx.dispatch_action(HandleInput("AAAA".to_string()));
16836    cx.assert_editor_state(
16837        &r#"AAAAˇone
16838        two
16839
16840        three
16841        fourAAAAˇ
16842        five
16843
16844        siAAAAˇx"#
16845            .unindent(),
16846    );
16847}
16848
16849#[gpui::test]
16850async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16851    init_test(cx, |_| {});
16852
16853    let mut cx = EditorTestContext::new(cx).await;
16854    cx.set_state(
16855        r#"let foo = 1;
16856let foo = 2;
16857let foo = 3;
16858let fooˇ = 4;
16859let foo = 5;
16860let foo = 6;
16861let foo = 7;
16862let foo = 8;
16863let foo = 9;
16864let foo = 10;
16865let foo = 11;
16866let foo = 12;
16867let foo = 13;
16868let foo = 14;
16869let foo = 15;"#,
16870    );
16871
16872    cx.update_editor(|e, window, cx| {
16873        assert_eq!(
16874            e.next_scroll_position,
16875            NextScrollCursorCenterTopBottom::Center,
16876            "Default next scroll direction is center",
16877        );
16878
16879        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16880        assert_eq!(
16881            e.next_scroll_position,
16882            NextScrollCursorCenterTopBottom::Top,
16883            "After center, next scroll direction should be top",
16884        );
16885
16886        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16887        assert_eq!(
16888            e.next_scroll_position,
16889            NextScrollCursorCenterTopBottom::Bottom,
16890            "After top, next scroll direction should be bottom",
16891        );
16892
16893        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16894        assert_eq!(
16895            e.next_scroll_position,
16896            NextScrollCursorCenterTopBottom::Center,
16897            "After bottom, scrolling should start over",
16898        );
16899
16900        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16901        assert_eq!(
16902            e.next_scroll_position,
16903            NextScrollCursorCenterTopBottom::Top,
16904            "Scrolling continues if retriggered fast enough"
16905        );
16906    });
16907
16908    cx.executor()
16909        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16910    cx.executor().run_until_parked();
16911    cx.update_editor(|e, _, _| {
16912        assert_eq!(
16913            e.next_scroll_position,
16914            NextScrollCursorCenterTopBottom::Center,
16915            "If scrolling is not triggered fast enough, it should reset"
16916        );
16917    });
16918}
16919
16920#[gpui::test]
16921async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16922    init_test(cx, |_| {});
16923    let mut cx = EditorLspTestContext::new_rust(
16924        lsp::ServerCapabilities {
16925            definition_provider: Some(lsp::OneOf::Left(true)),
16926            references_provider: Some(lsp::OneOf::Left(true)),
16927            ..lsp::ServerCapabilities::default()
16928        },
16929        cx,
16930    )
16931    .await;
16932
16933    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16934        let go_to_definition = cx
16935            .lsp
16936            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
16937                move |params, _| async move {
16938                    if empty_go_to_definition {
16939                        Ok(None)
16940                    } else {
16941                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16942                            uri: params.text_document_position_params.text_document.uri,
16943                            range: lsp::Range::new(
16944                                lsp::Position::new(4, 3),
16945                                lsp::Position::new(4, 6),
16946                            ),
16947                        })))
16948                    }
16949                },
16950            );
16951        let references = cx
16952            .lsp
16953            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
16954                Ok(Some(vec![lsp::Location {
16955                    uri: params.text_document_position.text_document.uri,
16956                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16957                }]))
16958            });
16959        (go_to_definition, references)
16960    };
16961
16962    cx.set_state(
16963        &r#"fn one() {
16964            let mut a = ˇtwo();
16965        }
16966
16967        fn two() {}"#
16968            .unindent(),
16969    );
16970    set_up_lsp_handlers(false, &mut cx);
16971    let navigated = cx
16972        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16973        .await
16974        .expect("Failed to navigate to definition");
16975    assert_eq!(
16976        navigated,
16977        Navigated::Yes,
16978        "Should have navigated to definition from the GetDefinition response"
16979    );
16980    cx.assert_editor_state(
16981        &r#"fn one() {
16982            let mut a = two();
16983        }
16984
16985        fn «twoˇ»() {}"#
16986            .unindent(),
16987    );
16988
16989    let editors = cx.update_workspace(|workspace, _, cx| {
16990        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16991    });
16992    cx.update_editor(|_, _, test_editor_cx| {
16993        assert_eq!(
16994            editors.len(),
16995            1,
16996            "Initially, only one, test, editor should be open in the workspace"
16997        );
16998        assert_eq!(
16999            test_editor_cx.entity(),
17000            editors.last().expect("Asserted len is 1").clone()
17001        );
17002    });
17003
17004    set_up_lsp_handlers(true, &mut cx);
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    assert_eq!(
17010        navigated,
17011        Navigated::Yes,
17012        "Should have navigated to references as a fallback after empty GoToDefinition response"
17013    );
17014    // We should not change the selections in the existing file,
17015    // if opening another milti buffer with the references
17016    cx.assert_editor_state(
17017        &r#"fn one() {
17018            let mut a = two();
17019        }
17020
17021        fn «twoˇ»() {}"#
17022            .unindent(),
17023    );
17024    let editors = cx.update_workspace(|workspace, _, cx| {
17025        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17026    });
17027    cx.update_editor(|_, _, test_editor_cx| {
17028        assert_eq!(
17029            editors.len(),
17030            2,
17031            "After falling back to references search, we open a new editor with the results"
17032        );
17033        let references_fallback_text = editors
17034            .into_iter()
17035            .find(|new_editor| *new_editor != test_editor_cx.entity())
17036            .expect("Should have one non-test editor now")
17037            .read(test_editor_cx)
17038            .text(test_editor_cx);
17039        assert_eq!(
17040            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17041            "Should use the range from the references response and not the GoToDefinition one"
17042        );
17043    });
17044}
17045
17046#[gpui::test]
17047async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17048    init_test(cx, |_| {});
17049    cx.update(|cx| {
17050        let mut editor_settings = EditorSettings::get_global(cx).clone();
17051        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17052        EditorSettings::override_global(editor_settings, cx);
17053    });
17054    let mut cx = EditorLspTestContext::new_rust(
17055        lsp::ServerCapabilities {
17056            definition_provider: Some(lsp::OneOf::Left(true)),
17057            references_provider: Some(lsp::OneOf::Left(true)),
17058            ..lsp::ServerCapabilities::default()
17059        },
17060        cx,
17061    )
17062    .await;
17063    let original_state = r#"fn one() {
17064        let mut a = ˇtwo();
17065    }
17066
17067    fn two() {}"#
17068        .unindent();
17069    cx.set_state(&original_state);
17070
17071    let mut go_to_definition = cx
17072        .lsp
17073        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17074            move |_, _| async move { Ok(None) },
17075        );
17076    let _references = cx
17077        .lsp
17078        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17079            panic!("Should not call for references with no go to definition fallback")
17080        });
17081
17082    let navigated = cx
17083        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17084        .await
17085        .expect("Failed to navigate to lookup references");
17086    go_to_definition
17087        .next()
17088        .await
17089        .expect("Should have called the go_to_definition handler");
17090
17091    assert_eq!(
17092        navigated,
17093        Navigated::No,
17094        "Should have navigated to references as a fallback after empty GoToDefinition response"
17095    );
17096    cx.assert_editor_state(&original_state);
17097    let editors = cx.update_workspace(|workspace, _, cx| {
17098        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17099    });
17100    cx.update_editor(|_, _, _| {
17101        assert_eq!(
17102            editors.len(),
17103            1,
17104            "After unsuccessful fallback, no other editor should have been opened"
17105        );
17106    });
17107}
17108
17109#[gpui::test]
17110async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17111    init_test(cx, |_| {});
17112
17113    let language = Arc::new(Language::new(
17114        LanguageConfig::default(),
17115        Some(tree_sitter_rust::LANGUAGE.into()),
17116    ));
17117
17118    let text = r#"
17119        #[cfg(test)]
17120        mod tests() {
17121            #[test]
17122            fn runnable_1() {
17123                let a = 1;
17124            }
17125
17126            #[test]
17127            fn runnable_2() {
17128                let a = 1;
17129                let b = 2;
17130            }
17131        }
17132    "#
17133    .unindent();
17134
17135    let fs = FakeFs::new(cx.executor());
17136    fs.insert_file("/file.rs", Default::default()).await;
17137
17138    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17139    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17140    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17141    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17142    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17143
17144    let editor = cx.new_window_entity(|window, cx| {
17145        Editor::new(
17146            EditorMode::Full,
17147            multi_buffer,
17148            Some(project.clone()),
17149            window,
17150            cx,
17151        )
17152    });
17153
17154    editor.update_in(cx, |editor, window, cx| {
17155        let snapshot = editor.buffer().read(cx).snapshot(cx);
17156        editor.tasks.insert(
17157            (buffer.read(cx).remote_id(), 3),
17158            RunnableTasks {
17159                templates: vec![],
17160                offset: snapshot.anchor_before(43),
17161                column: 0,
17162                extra_variables: HashMap::default(),
17163                context_range: BufferOffset(43)..BufferOffset(85),
17164            },
17165        );
17166        editor.tasks.insert(
17167            (buffer.read(cx).remote_id(), 8),
17168            RunnableTasks {
17169                templates: vec![],
17170                offset: snapshot.anchor_before(86),
17171                column: 0,
17172                extra_variables: HashMap::default(),
17173                context_range: BufferOffset(86)..BufferOffset(191),
17174            },
17175        );
17176
17177        // Test finding task when cursor is inside function body
17178        editor.change_selections(None, window, cx, |s| {
17179            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17180        });
17181        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17182        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17183
17184        // Test finding task when cursor is on function name
17185        editor.change_selections(None, window, cx, |s| {
17186            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17187        });
17188        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17189        assert_eq!(row, 8, "Should find task when cursor is on function name");
17190    });
17191}
17192
17193#[gpui::test]
17194async fn test_folding_buffers(cx: &mut TestAppContext) {
17195    init_test(cx, |_| {});
17196
17197    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17198    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17199    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17200
17201    let fs = FakeFs::new(cx.executor());
17202    fs.insert_tree(
17203        path!("/a"),
17204        json!({
17205            "first.rs": sample_text_1,
17206            "second.rs": sample_text_2,
17207            "third.rs": sample_text_3,
17208        }),
17209    )
17210    .await;
17211    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17212    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17213    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17214    let worktree = project.update(cx, |project, cx| {
17215        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17216        assert_eq!(worktrees.len(), 1);
17217        worktrees.pop().unwrap()
17218    });
17219    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17220
17221    let buffer_1 = project
17222        .update(cx, |project, cx| {
17223            project.open_buffer((worktree_id, "first.rs"), cx)
17224        })
17225        .await
17226        .unwrap();
17227    let buffer_2 = project
17228        .update(cx, |project, cx| {
17229            project.open_buffer((worktree_id, "second.rs"), cx)
17230        })
17231        .await
17232        .unwrap();
17233    let buffer_3 = project
17234        .update(cx, |project, cx| {
17235            project.open_buffer((worktree_id, "third.rs"), cx)
17236        })
17237        .await
17238        .unwrap();
17239
17240    let multi_buffer = cx.new(|cx| {
17241        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17242        multi_buffer.push_excerpts(
17243            buffer_1.clone(),
17244            [
17245                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17246                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17247                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17248            ],
17249            cx,
17250        );
17251        multi_buffer.push_excerpts(
17252            buffer_2.clone(),
17253            [
17254                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17255                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17256                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17257            ],
17258            cx,
17259        );
17260        multi_buffer.push_excerpts(
17261            buffer_3.clone(),
17262            [
17263                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17264                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17265                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17266            ],
17267            cx,
17268        );
17269        multi_buffer
17270    });
17271    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17272        Editor::new(
17273            EditorMode::Full,
17274            multi_buffer.clone(),
17275            Some(project.clone()),
17276            window,
17277            cx,
17278        )
17279    });
17280
17281    assert_eq!(
17282        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17283        "\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",
17284    );
17285
17286    multi_buffer_editor.update(cx, |editor, cx| {
17287        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17288    });
17289    assert_eq!(
17290        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17291        "\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",
17292        "After folding the first buffer, its text should not be displayed"
17293    );
17294
17295    multi_buffer_editor.update(cx, |editor, cx| {
17296        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17297    });
17298    assert_eq!(
17299        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17300        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17301        "After folding the second buffer, its text should not be displayed"
17302    );
17303
17304    multi_buffer_editor.update(cx, |editor, cx| {
17305        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17306    });
17307    assert_eq!(
17308        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17309        "\n\n\n\n\n",
17310        "After folding the third buffer, its text should not be displayed"
17311    );
17312
17313    // Emulate selection inside the fold logic, that should work
17314    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17315        editor
17316            .snapshot(window, cx)
17317            .next_line_boundary(Point::new(0, 4));
17318    });
17319
17320    multi_buffer_editor.update(cx, |editor, cx| {
17321        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17322    });
17323    assert_eq!(
17324        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17325        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17326        "After unfolding the second buffer, its text should be displayed"
17327    );
17328
17329    // Typing inside of buffer 1 causes that buffer to be unfolded.
17330    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17331        assert_eq!(
17332            multi_buffer
17333                .read(cx)
17334                .snapshot(cx)
17335                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17336                .collect::<String>(),
17337            "bbbb"
17338        );
17339        editor.change_selections(None, window, cx, |selections| {
17340            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17341        });
17342        editor.handle_input("B", window, cx);
17343    });
17344
17345    assert_eq!(
17346        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17347        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17348        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17349    );
17350
17351    multi_buffer_editor.update(cx, |editor, cx| {
17352        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17353    });
17354    assert_eq!(
17355        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17356        "\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",
17357        "After unfolding the all buffers, all original text should be displayed"
17358    );
17359}
17360
17361#[gpui::test]
17362async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17363    init_test(cx, |_| {});
17364
17365    let sample_text_1 = "1111\n2222\n3333".to_string();
17366    let sample_text_2 = "4444\n5555\n6666".to_string();
17367    let sample_text_3 = "7777\n8888\n9999".to_string();
17368
17369    let fs = FakeFs::new(cx.executor());
17370    fs.insert_tree(
17371        path!("/a"),
17372        json!({
17373            "first.rs": sample_text_1,
17374            "second.rs": sample_text_2,
17375            "third.rs": sample_text_3,
17376        }),
17377    )
17378    .await;
17379    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17380    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17381    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17382    let worktree = project.update(cx, |project, cx| {
17383        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17384        assert_eq!(worktrees.len(), 1);
17385        worktrees.pop().unwrap()
17386    });
17387    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17388
17389    let buffer_1 = project
17390        .update(cx, |project, cx| {
17391            project.open_buffer((worktree_id, "first.rs"), cx)
17392        })
17393        .await
17394        .unwrap();
17395    let buffer_2 = project
17396        .update(cx, |project, cx| {
17397            project.open_buffer((worktree_id, "second.rs"), cx)
17398        })
17399        .await
17400        .unwrap();
17401    let buffer_3 = project
17402        .update(cx, |project, cx| {
17403            project.open_buffer((worktree_id, "third.rs"), cx)
17404        })
17405        .await
17406        .unwrap();
17407
17408    let multi_buffer = cx.new(|cx| {
17409        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17410        multi_buffer.push_excerpts(
17411            buffer_1.clone(),
17412            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17413            cx,
17414        );
17415        multi_buffer.push_excerpts(
17416            buffer_2.clone(),
17417            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17418            cx,
17419        );
17420        multi_buffer.push_excerpts(
17421            buffer_3.clone(),
17422            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
17423            cx,
17424        );
17425        multi_buffer
17426    });
17427
17428    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17429        Editor::new(
17430            EditorMode::Full,
17431            multi_buffer,
17432            Some(project.clone()),
17433            window,
17434            cx,
17435        )
17436    });
17437
17438    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
17439    assert_eq!(
17440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17441        full_text,
17442    );
17443
17444    multi_buffer_editor.update(cx, |editor, cx| {
17445        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17446    });
17447    assert_eq!(
17448        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17449        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
17450        "After folding the first buffer, its text should not be displayed"
17451    );
17452
17453    multi_buffer_editor.update(cx, |editor, cx| {
17454        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17455    });
17456
17457    assert_eq!(
17458        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17459        "\n\n\n\n\n\n7777\n8888\n9999",
17460        "After folding the second buffer, its text should not be displayed"
17461    );
17462
17463    multi_buffer_editor.update(cx, |editor, cx| {
17464        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17465    });
17466    assert_eq!(
17467        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17468        "\n\n\n\n\n",
17469        "After folding the third buffer, its text should not be displayed"
17470    );
17471
17472    multi_buffer_editor.update(cx, |editor, cx| {
17473        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17474    });
17475    assert_eq!(
17476        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17477        "\n\n\n\n4444\n5555\n6666\n\n",
17478        "After unfolding the second buffer, its text should be displayed"
17479    );
17480
17481    multi_buffer_editor.update(cx, |editor, cx| {
17482        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
17483    });
17484    assert_eq!(
17485        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17486        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
17487        "After unfolding the first buffer, its text should be displayed"
17488    );
17489
17490    multi_buffer_editor.update(cx, |editor, cx| {
17491        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17492    });
17493    assert_eq!(
17494        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17495        full_text,
17496        "After unfolding all buffers, all original text should be displayed"
17497    );
17498}
17499
17500#[gpui::test]
17501async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
17502    init_test(cx, |_| {});
17503
17504    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17505
17506    let fs = FakeFs::new(cx.executor());
17507    fs.insert_tree(
17508        path!("/a"),
17509        json!({
17510            "main.rs": sample_text,
17511        }),
17512    )
17513    .await;
17514    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17515    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17516    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17517    let worktree = project.update(cx, |project, cx| {
17518        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17519        assert_eq!(worktrees.len(), 1);
17520        worktrees.pop().unwrap()
17521    });
17522    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17523
17524    let buffer_1 = project
17525        .update(cx, |project, cx| {
17526            project.open_buffer((worktree_id, "main.rs"), cx)
17527        })
17528        .await
17529        .unwrap();
17530
17531    let multi_buffer = cx.new(|cx| {
17532        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17533        multi_buffer.push_excerpts(
17534            buffer_1.clone(),
17535            [ExcerptRange::new(
17536                Point::new(0, 0)
17537                    ..Point::new(
17538                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
17539                        0,
17540                    ),
17541            )],
17542            cx,
17543        );
17544        multi_buffer
17545    });
17546    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17547        Editor::new(
17548            EditorMode::Full,
17549            multi_buffer,
17550            Some(project.clone()),
17551            window,
17552            cx,
17553        )
17554    });
17555
17556    let selection_range = Point::new(1, 0)..Point::new(2, 0);
17557    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17558        enum TestHighlight {}
17559        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
17560        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
17561        editor.highlight_text::<TestHighlight>(
17562            vec![highlight_range.clone()],
17563            HighlightStyle::color(Hsla::green()),
17564            cx,
17565        );
17566        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
17567    });
17568
17569    let full_text = format!("\n\n{sample_text}");
17570    assert_eq!(
17571        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17572        full_text,
17573    );
17574}
17575
17576#[gpui::test]
17577async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
17578    init_test(cx, |_| {});
17579    cx.update(|cx| {
17580        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
17581            "keymaps/default-linux.json",
17582            cx,
17583        )
17584        .unwrap();
17585        cx.bind_keys(default_key_bindings);
17586    });
17587
17588    let (editor, cx) = cx.add_window_view(|window, cx| {
17589        let multi_buffer = MultiBuffer::build_multi(
17590            [
17591                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
17592                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
17593                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
17594                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
17595            ],
17596            cx,
17597        );
17598        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
17599
17600        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
17601        // fold all but the second buffer, so that we test navigating between two
17602        // adjacent folded buffers, as well as folded buffers at the start and
17603        // end the multibuffer
17604        editor.fold_buffer(buffer_ids[0], cx);
17605        editor.fold_buffer(buffer_ids[2], cx);
17606        editor.fold_buffer(buffer_ids[3], cx);
17607
17608        editor
17609    });
17610    cx.simulate_resize(size(px(1000.), px(1000.)));
17611
17612    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17613    cx.assert_excerpts_with_selections(indoc! {"
17614        [EXCERPT]
17615        ˇ[FOLDED]
17616        [EXCERPT]
17617        a1
17618        b1
17619        [EXCERPT]
17620        [FOLDED]
17621        [EXCERPT]
17622        [FOLDED]
17623        "
17624    });
17625    cx.simulate_keystroke("down");
17626    cx.assert_excerpts_with_selections(indoc! {"
17627        [EXCERPT]
17628        [FOLDED]
17629        [EXCERPT]
17630        ˇa1
17631        b1
17632        [EXCERPT]
17633        [FOLDED]
17634        [EXCERPT]
17635        [FOLDED]
17636        "
17637    });
17638    cx.simulate_keystroke("down");
17639    cx.assert_excerpts_with_selections(indoc! {"
17640        [EXCERPT]
17641        [FOLDED]
17642        [EXCERPT]
17643        a1
17644        ˇb1
17645        [EXCERPT]
17646        [FOLDED]
17647        [EXCERPT]
17648        [FOLDED]
17649        "
17650    });
17651    cx.simulate_keystroke("down");
17652    cx.assert_excerpts_with_selections(indoc! {"
17653        [EXCERPT]
17654        [FOLDED]
17655        [EXCERPT]
17656        a1
17657        b1
17658        ˇ[EXCERPT]
17659        [FOLDED]
17660        [EXCERPT]
17661        [FOLDED]
17662        "
17663    });
17664    cx.simulate_keystroke("down");
17665    cx.assert_excerpts_with_selections(indoc! {"
17666        [EXCERPT]
17667        [FOLDED]
17668        [EXCERPT]
17669        a1
17670        b1
17671        [EXCERPT]
17672        ˇ[FOLDED]
17673        [EXCERPT]
17674        [FOLDED]
17675        "
17676    });
17677    for _ in 0..5 {
17678        cx.simulate_keystroke("down");
17679        cx.assert_excerpts_with_selections(indoc! {"
17680            [EXCERPT]
17681            [FOLDED]
17682            [EXCERPT]
17683            a1
17684            b1
17685            [EXCERPT]
17686            [FOLDED]
17687            [EXCERPT]
17688            ˇ[FOLDED]
17689            "
17690        });
17691    }
17692
17693    cx.simulate_keystroke("up");
17694    cx.assert_excerpts_with_selections(indoc! {"
17695        [EXCERPT]
17696        [FOLDED]
17697        [EXCERPT]
17698        a1
17699        b1
17700        [EXCERPT]
17701        ˇ[FOLDED]
17702        [EXCERPT]
17703        [FOLDED]
17704        "
17705    });
17706    cx.simulate_keystroke("up");
17707    cx.assert_excerpts_with_selections(indoc! {"
17708        [EXCERPT]
17709        [FOLDED]
17710        [EXCERPT]
17711        a1
17712        b1
17713        ˇ[EXCERPT]
17714        [FOLDED]
17715        [EXCERPT]
17716        [FOLDED]
17717        "
17718    });
17719    cx.simulate_keystroke("up");
17720    cx.assert_excerpts_with_selections(indoc! {"
17721        [EXCERPT]
17722        [FOLDED]
17723        [EXCERPT]
17724        a1
17725        ˇb1
17726        [EXCERPT]
17727        [FOLDED]
17728        [EXCERPT]
17729        [FOLDED]
17730        "
17731    });
17732    cx.simulate_keystroke("up");
17733    cx.assert_excerpts_with_selections(indoc! {"
17734        [EXCERPT]
17735        [FOLDED]
17736        [EXCERPT]
17737        ˇa1
17738        b1
17739        [EXCERPT]
17740        [FOLDED]
17741        [EXCERPT]
17742        [FOLDED]
17743        "
17744    });
17745    for _ in 0..5 {
17746        cx.simulate_keystroke("up");
17747        cx.assert_excerpts_with_selections(indoc! {"
17748            [EXCERPT]
17749            ˇ[FOLDED]
17750            [EXCERPT]
17751            a1
17752            b1
17753            [EXCERPT]
17754            [FOLDED]
17755            [EXCERPT]
17756            [FOLDED]
17757            "
17758        });
17759    }
17760}
17761
17762#[gpui::test]
17763async fn test_inline_completion_text(cx: &mut TestAppContext) {
17764    init_test(cx, |_| {});
17765
17766    // Simple insertion
17767    assert_highlighted_edits(
17768        "Hello, world!",
17769        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
17770        true,
17771        cx,
17772        |highlighted_edits, cx| {
17773            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
17774            assert_eq!(highlighted_edits.highlights.len(), 1);
17775            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
17776            assert_eq!(
17777                highlighted_edits.highlights[0].1.background_color,
17778                Some(cx.theme().status().created_background)
17779            );
17780        },
17781    )
17782    .await;
17783
17784    // Replacement
17785    assert_highlighted_edits(
17786        "This is a test.",
17787        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17788        false,
17789        cx,
17790        |highlighted_edits, cx| {
17791            assert_eq!(highlighted_edits.text, "That is a test.");
17792            assert_eq!(highlighted_edits.highlights.len(), 1);
17793            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17794            assert_eq!(
17795                highlighted_edits.highlights[0].1.background_color,
17796                Some(cx.theme().status().created_background)
17797            );
17798        },
17799    )
17800    .await;
17801
17802    // Multiple edits
17803    assert_highlighted_edits(
17804        "Hello, world!",
17805        vec![
17806            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17807            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17808        ],
17809        false,
17810        cx,
17811        |highlighted_edits, cx| {
17812            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17813            assert_eq!(highlighted_edits.highlights.len(), 2);
17814            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17815            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17816            assert_eq!(
17817                highlighted_edits.highlights[0].1.background_color,
17818                Some(cx.theme().status().created_background)
17819            );
17820            assert_eq!(
17821                highlighted_edits.highlights[1].1.background_color,
17822                Some(cx.theme().status().created_background)
17823            );
17824        },
17825    )
17826    .await;
17827
17828    // Multiple lines with edits
17829    assert_highlighted_edits(
17830        "First line\nSecond line\nThird line\nFourth line",
17831        vec![
17832            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17833            (
17834                Point::new(2, 0)..Point::new(2, 10),
17835                "New third line".to_string(),
17836            ),
17837            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17838        ],
17839        false,
17840        cx,
17841        |highlighted_edits, cx| {
17842            assert_eq!(
17843                highlighted_edits.text,
17844                "Second modified\nNew third line\nFourth updated line"
17845            );
17846            assert_eq!(highlighted_edits.highlights.len(), 3);
17847            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17848            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17849            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17850            for highlight in &highlighted_edits.highlights {
17851                assert_eq!(
17852                    highlight.1.background_color,
17853                    Some(cx.theme().status().created_background)
17854                );
17855            }
17856        },
17857    )
17858    .await;
17859}
17860
17861#[gpui::test]
17862async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17863    init_test(cx, |_| {});
17864
17865    // Deletion
17866    assert_highlighted_edits(
17867        "Hello, world!",
17868        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17869        true,
17870        cx,
17871        |highlighted_edits, cx| {
17872            assert_eq!(highlighted_edits.text, "Hello, world!");
17873            assert_eq!(highlighted_edits.highlights.len(), 1);
17874            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17875            assert_eq!(
17876                highlighted_edits.highlights[0].1.background_color,
17877                Some(cx.theme().status().deleted_background)
17878            );
17879        },
17880    )
17881    .await;
17882
17883    // Insertion
17884    assert_highlighted_edits(
17885        "Hello, world!",
17886        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17887        true,
17888        cx,
17889        |highlighted_edits, cx| {
17890            assert_eq!(highlighted_edits.highlights.len(), 1);
17891            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17892            assert_eq!(
17893                highlighted_edits.highlights[0].1.background_color,
17894                Some(cx.theme().status().created_background)
17895            );
17896        },
17897    )
17898    .await;
17899}
17900
17901async fn assert_highlighted_edits(
17902    text: &str,
17903    edits: Vec<(Range<Point>, String)>,
17904    include_deletions: bool,
17905    cx: &mut TestAppContext,
17906    assertion_fn: impl Fn(HighlightedText, &App),
17907) {
17908    let window = cx.add_window(|window, cx| {
17909        let buffer = MultiBuffer::build_simple(text, cx);
17910        Editor::new(EditorMode::Full, buffer, None, window, cx)
17911    });
17912    let cx = &mut VisualTestContext::from_window(*window, cx);
17913
17914    let (buffer, snapshot) = window
17915        .update(cx, |editor, _window, cx| {
17916            (
17917                editor.buffer().clone(),
17918                editor.buffer().read(cx).snapshot(cx),
17919            )
17920        })
17921        .unwrap();
17922
17923    let edits = edits
17924        .into_iter()
17925        .map(|(range, edit)| {
17926            (
17927                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17928                edit,
17929            )
17930        })
17931        .collect::<Vec<_>>();
17932
17933    let text_anchor_edits = edits
17934        .clone()
17935        .into_iter()
17936        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17937        .collect::<Vec<_>>();
17938
17939    let edit_preview = window
17940        .update(cx, |_, _window, cx| {
17941            buffer
17942                .read(cx)
17943                .as_singleton()
17944                .unwrap()
17945                .read(cx)
17946                .preview_edits(text_anchor_edits.into(), cx)
17947        })
17948        .unwrap()
17949        .await;
17950
17951    cx.update(|_window, cx| {
17952        let highlighted_edits = inline_completion_edit_text(
17953            &snapshot.as_singleton().unwrap().2,
17954            &edits,
17955            &edit_preview,
17956            include_deletions,
17957            cx,
17958        );
17959        assertion_fn(highlighted_edits, cx)
17960    });
17961}
17962
17963#[track_caller]
17964fn assert_breakpoint(
17965    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
17966    path: &Arc<Path>,
17967    expected: Vec<(u32, Breakpoint)>,
17968) {
17969    if expected.len() == 0usize {
17970        assert!(!breakpoints.contains_key(path), "{}", path.display());
17971    } else {
17972        let mut breakpoint = breakpoints
17973            .get(path)
17974            .unwrap()
17975            .into_iter()
17976            .map(|breakpoint| {
17977                (
17978                    breakpoint.row,
17979                    Breakpoint {
17980                        message: breakpoint.message.clone(),
17981                        state: breakpoint.state,
17982                        condition: breakpoint.condition.clone(),
17983                        hit_condition: breakpoint.hit_condition.clone(),
17984                    },
17985                )
17986            })
17987            .collect::<Vec<_>>();
17988
17989        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17990
17991        assert_eq!(expected, breakpoint);
17992    }
17993}
17994
17995fn add_log_breakpoint_at_cursor(
17996    editor: &mut Editor,
17997    log_message: &str,
17998    window: &mut Window,
17999    cx: &mut Context<Editor>,
18000) {
18001    let (anchor, bp) = editor
18002        .breakpoint_at_cursor_head(window, cx)
18003        .unwrap_or_else(|| {
18004            let cursor_position: Point = editor.selections.newest(cx).head();
18005
18006            let breakpoint_position = editor
18007                .snapshot(window, cx)
18008                .display_snapshot
18009                .buffer_snapshot
18010                .anchor_before(Point::new(cursor_position.row, 0));
18011
18012            (breakpoint_position, Breakpoint::new_log(&log_message))
18013        });
18014
18015    editor.edit_breakpoint_at_anchor(
18016        anchor,
18017        bp,
18018        BreakpointEditAction::EditLogMessage(log_message.into()),
18019        cx,
18020    );
18021}
18022
18023#[gpui::test]
18024async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18025    init_test(cx, |_| {});
18026
18027    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18028    let fs = FakeFs::new(cx.executor());
18029    fs.insert_tree(
18030        path!("/a"),
18031        json!({
18032            "main.rs": sample_text,
18033        }),
18034    )
18035    .await;
18036    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18037    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18038    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18039
18040    let fs = FakeFs::new(cx.executor());
18041    fs.insert_tree(
18042        path!("/a"),
18043        json!({
18044            "main.rs": sample_text,
18045        }),
18046    )
18047    .await;
18048    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18049    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18050    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18051    let worktree_id = workspace
18052        .update(cx, |workspace, _window, cx| {
18053            workspace.project().update(cx, |project, cx| {
18054                project.worktrees(cx).next().unwrap().read(cx).id()
18055            })
18056        })
18057        .unwrap();
18058
18059    let buffer = project
18060        .update(cx, |project, cx| {
18061            project.open_buffer((worktree_id, "main.rs"), cx)
18062        })
18063        .await
18064        .unwrap();
18065
18066    let (editor, cx) = cx.add_window_view(|window, cx| {
18067        Editor::new(
18068            EditorMode::Full,
18069            MultiBuffer::build_from_buffer(buffer, cx),
18070            Some(project.clone()),
18071            window,
18072            cx,
18073        )
18074    });
18075
18076    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18077    let abs_path = project.read_with(cx, |project, cx| {
18078        project
18079            .absolute_path(&project_path, cx)
18080            .map(|path_buf| Arc::from(path_buf.to_owned()))
18081            .unwrap()
18082    });
18083
18084    // assert we can add breakpoint on the first line
18085    editor.update_in(cx, |editor, window, cx| {
18086        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18087        editor.move_to_end(&MoveToEnd, window, cx);
18088        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18089    });
18090
18091    let breakpoints = editor.update(cx, |editor, cx| {
18092        editor
18093            .breakpoint_store()
18094            .as_ref()
18095            .unwrap()
18096            .read(cx)
18097            .all_breakpoints(cx)
18098            .clone()
18099    });
18100
18101    assert_eq!(1, breakpoints.len());
18102    assert_breakpoint(
18103        &breakpoints,
18104        &abs_path,
18105        vec![
18106            (0, Breakpoint::new_standard()),
18107            (3, Breakpoint::new_standard()),
18108        ],
18109    );
18110
18111    editor.update_in(cx, |editor, window, cx| {
18112        editor.move_to_beginning(&MoveToBeginning, window, cx);
18113        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18114    });
18115
18116    let breakpoints = editor.update(cx, |editor, cx| {
18117        editor
18118            .breakpoint_store()
18119            .as_ref()
18120            .unwrap()
18121            .read(cx)
18122            .all_breakpoints(cx)
18123            .clone()
18124    });
18125
18126    assert_eq!(1, breakpoints.len());
18127    assert_breakpoint(
18128        &breakpoints,
18129        &abs_path,
18130        vec![(3, Breakpoint::new_standard())],
18131    );
18132
18133    editor.update_in(cx, |editor, window, cx| {
18134        editor.move_to_end(&MoveToEnd, window, cx);
18135        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18136    });
18137
18138    let breakpoints = editor.update(cx, |editor, cx| {
18139        editor
18140            .breakpoint_store()
18141            .as_ref()
18142            .unwrap()
18143            .read(cx)
18144            .all_breakpoints(cx)
18145            .clone()
18146    });
18147
18148    assert_eq!(0, breakpoints.len());
18149    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18150}
18151
18152#[gpui::test]
18153async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18154    init_test(cx, |_| {});
18155
18156    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18157
18158    let fs = FakeFs::new(cx.executor());
18159    fs.insert_tree(
18160        path!("/a"),
18161        json!({
18162            "main.rs": sample_text,
18163        }),
18164    )
18165    .await;
18166    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18167    let (workspace, cx) =
18168        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18169
18170    let worktree_id = workspace.update(cx, |workspace, cx| {
18171        workspace.project().update(cx, |project, cx| {
18172            project.worktrees(cx).next().unwrap().read(cx).id()
18173        })
18174    });
18175
18176    let buffer = project
18177        .update(cx, |project, cx| {
18178            project.open_buffer((worktree_id, "main.rs"), cx)
18179        })
18180        .await
18181        .unwrap();
18182
18183    let (editor, cx) = cx.add_window_view(|window, cx| {
18184        Editor::new(
18185            EditorMode::Full,
18186            MultiBuffer::build_from_buffer(buffer, cx),
18187            Some(project.clone()),
18188            window,
18189            cx,
18190        )
18191    });
18192
18193    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18194    let abs_path = project.read_with(cx, |project, cx| {
18195        project
18196            .absolute_path(&project_path, cx)
18197            .map(|path_buf| Arc::from(path_buf.to_owned()))
18198            .unwrap()
18199    });
18200
18201    editor.update_in(cx, |editor, window, cx| {
18202        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18203    });
18204
18205    let breakpoints = editor.update(cx, |editor, cx| {
18206        editor
18207            .breakpoint_store()
18208            .as_ref()
18209            .unwrap()
18210            .read(cx)
18211            .all_breakpoints(cx)
18212            .clone()
18213    });
18214
18215    assert_breakpoint(
18216        &breakpoints,
18217        &abs_path,
18218        vec![(0, Breakpoint::new_log("hello world"))],
18219    );
18220
18221    // Removing a log message from a log breakpoint should remove it
18222    editor.update_in(cx, |editor, window, cx| {
18223        add_log_breakpoint_at_cursor(editor, "", window, cx);
18224    });
18225
18226    let breakpoints = editor.update(cx, |editor, cx| {
18227        editor
18228            .breakpoint_store()
18229            .as_ref()
18230            .unwrap()
18231            .read(cx)
18232            .all_breakpoints(cx)
18233            .clone()
18234    });
18235
18236    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18237
18238    editor.update_in(cx, |editor, window, cx| {
18239        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18240        editor.move_to_end(&MoveToEnd, window, cx);
18241        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18242        // Not adding a log message to a standard breakpoint shouldn't remove it
18243        add_log_breakpoint_at_cursor(editor, "", window, cx);
18244    });
18245
18246    let breakpoints = editor.update(cx, |editor, cx| {
18247        editor
18248            .breakpoint_store()
18249            .as_ref()
18250            .unwrap()
18251            .read(cx)
18252            .all_breakpoints(cx)
18253            .clone()
18254    });
18255
18256    assert_breakpoint(
18257        &breakpoints,
18258        &abs_path,
18259        vec![
18260            (0, Breakpoint::new_standard()),
18261            (3, Breakpoint::new_standard()),
18262        ],
18263    );
18264
18265    editor.update_in(cx, |editor, window, cx| {
18266        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18267    });
18268
18269    let breakpoints = editor.update(cx, |editor, cx| {
18270        editor
18271            .breakpoint_store()
18272            .as_ref()
18273            .unwrap()
18274            .read(cx)
18275            .all_breakpoints(cx)
18276            .clone()
18277    });
18278
18279    assert_breakpoint(
18280        &breakpoints,
18281        &abs_path,
18282        vec![
18283            (0, Breakpoint::new_standard()),
18284            (3, Breakpoint::new_log("hello world")),
18285        ],
18286    );
18287
18288    editor.update_in(cx, |editor, window, cx| {
18289        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18290    });
18291
18292    let breakpoints = editor.update(cx, |editor, cx| {
18293        editor
18294            .breakpoint_store()
18295            .as_ref()
18296            .unwrap()
18297            .read(cx)
18298            .all_breakpoints(cx)
18299            .clone()
18300    });
18301
18302    assert_breakpoint(
18303        &breakpoints,
18304        &abs_path,
18305        vec![
18306            (0, Breakpoint::new_standard()),
18307            (3, Breakpoint::new_log("hello Earth!!")),
18308        ],
18309    );
18310}
18311
18312/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18313/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18314/// or when breakpoints were placed out of order. This tests for a regression too
18315#[gpui::test]
18316async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18317    init_test(cx, |_| {});
18318
18319    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18320    let fs = FakeFs::new(cx.executor());
18321    fs.insert_tree(
18322        path!("/a"),
18323        json!({
18324            "main.rs": sample_text,
18325        }),
18326    )
18327    .await;
18328    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18329    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18330    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18331
18332    let fs = FakeFs::new(cx.executor());
18333    fs.insert_tree(
18334        path!("/a"),
18335        json!({
18336            "main.rs": sample_text,
18337        }),
18338    )
18339    .await;
18340    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18341    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18342    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18343    let worktree_id = workspace
18344        .update(cx, |workspace, _window, cx| {
18345            workspace.project().update(cx, |project, cx| {
18346                project.worktrees(cx).next().unwrap().read(cx).id()
18347            })
18348        })
18349        .unwrap();
18350
18351    let buffer = project
18352        .update(cx, |project, cx| {
18353            project.open_buffer((worktree_id, "main.rs"), cx)
18354        })
18355        .await
18356        .unwrap();
18357
18358    let (editor, cx) = cx.add_window_view(|window, cx| {
18359        Editor::new(
18360            EditorMode::Full,
18361            MultiBuffer::build_from_buffer(buffer, cx),
18362            Some(project.clone()),
18363            window,
18364            cx,
18365        )
18366    });
18367
18368    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18369    let abs_path = project.read_with(cx, |project, cx| {
18370        project
18371            .absolute_path(&project_path, cx)
18372            .map(|path_buf| Arc::from(path_buf.to_owned()))
18373            .unwrap()
18374    });
18375
18376    // assert we can add breakpoint on the first line
18377    editor.update_in(cx, |editor, window, cx| {
18378        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18379        editor.move_to_end(&MoveToEnd, window, cx);
18380        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18381        editor.move_up(&MoveUp, window, cx);
18382        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18383    });
18384
18385    let breakpoints = editor.update(cx, |editor, cx| {
18386        editor
18387            .breakpoint_store()
18388            .as_ref()
18389            .unwrap()
18390            .read(cx)
18391            .all_breakpoints(cx)
18392            .clone()
18393    });
18394
18395    assert_eq!(1, breakpoints.len());
18396    assert_breakpoint(
18397        &breakpoints,
18398        &abs_path,
18399        vec![
18400            (0, Breakpoint::new_standard()),
18401            (2, Breakpoint::new_standard()),
18402            (3, Breakpoint::new_standard()),
18403        ],
18404    );
18405
18406    editor.update_in(cx, |editor, window, cx| {
18407        editor.move_to_beginning(&MoveToBeginning, window, cx);
18408        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18409        editor.move_to_end(&MoveToEnd, window, cx);
18410        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18411        // Disabling a breakpoint that doesn't exist should do nothing
18412        editor.move_up(&MoveUp, window, cx);
18413        editor.move_up(&MoveUp, window, cx);
18414        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18415    });
18416
18417    let breakpoints = editor.update(cx, |editor, cx| {
18418        editor
18419            .breakpoint_store()
18420            .as_ref()
18421            .unwrap()
18422            .read(cx)
18423            .all_breakpoints(cx)
18424            .clone()
18425    });
18426
18427    let disable_breakpoint = {
18428        let mut bp = Breakpoint::new_standard();
18429        bp.state = BreakpointState::Disabled;
18430        bp
18431    };
18432
18433    assert_eq!(1, breakpoints.len());
18434    assert_breakpoint(
18435        &breakpoints,
18436        &abs_path,
18437        vec![
18438            (0, disable_breakpoint.clone()),
18439            (2, Breakpoint::new_standard()),
18440            (3, disable_breakpoint.clone()),
18441        ],
18442    );
18443
18444    editor.update_in(cx, |editor, window, cx| {
18445        editor.move_to_beginning(&MoveToBeginning, window, cx);
18446        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18447        editor.move_to_end(&MoveToEnd, window, cx);
18448        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
18449        editor.move_up(&MoveUp, window, cx);
18450        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
18451    });
18452
18453    let breakpoints = editor.update(cx, |editor, cx| {
18454        editor
18455            .breakpoint_store()
18456            .as_ref()
18457            .unwrap()
18458            .read(cx)
18459            .all_breakpoints(cx)
18460            .clone()
18461    });
18462
18463    assert_eq!(1, breakpoints.len());
18464    assert_breakpoint(
18465        &breakpoints,
18466        &abs_path,
18467        vec![
18468            (0, Breakpoint::new_standard()),
18469            (2, disable_breakpoint),
18470            (3, Breakpoint::new_standard()),
18471        ],
18472    );
18473}
18474
18475#[gpui::test]
18476async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
18477    init_test(cx, |_| {});
18478    let capabilities = lsp::ServerCapabilities {
18479        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
18480            prepare_provider: Some(true),
18481            work_done_progress_options: Default::default(),
18482        })),
18483        ..Default::default()
18484    };
18485    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18486
18487    cx.set_state(indoc! {"
18488        struct Fˇoo {}
18489    "});
18490
18491    cx.update_editor(|editor, _, cx| {
18492        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18493        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18494        editor.highlight_background::<DocumentHighlightRead>(
18495            &[highlight_range],
18496            |c| c.editor_document_highlight_read_background,
18497            cx,
18498        );
18499    });
18500
18501    let mut prepare_rename_handler = cx
18502        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
18503            move |_, _, _| async move {
18504                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
18505                    start: lsp::Position {
18506                        line: 0,
18507                        character: 7,
18508                    },
18509                    end: lsp::Position {
18510                        line: 0,
18511                        character: 10,
18512                    },
18513                })))
18514            },
18515        );
18516    let prepare_rename_task = cx
18517        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18518        .expect("Prepare rename was not started");
18519    prepare_rename_handler.next().await.unwrap();
18520    prepare_rename_task.await.expect("Prepare rename failed");
18521
18522    let mut rename_handler =
18523        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18524            let edit = lsp::TextEdit {
18525                range: lsp::Range {
18526                    start: lsp::Position {
18527                        line: 0,
18528                        character: 7,
18529                    },
18530                    end: lsp::Position {
18531                        line: 0,
18532                        character: 10,
18533                    },
18534                },
18535                new_text: "FooRenamed".to_string(),
18536            };
18537            Ok(Some(lsp::WorkspaceEdit::new(
18538                // Specify the same edit twice
18539                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
18540            )))
18541        });
18542    let rename_task = cx
18543        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18544        .expect("Confirm rename was not started");
18545    rename_handler.next().await.unwrap();
18546    rename_task.await.expect("Confirm rename failed");
18547    cx.run_until_parked();
18548
18549    // Despite two edits, only one is actually applied as those are identical
18550    cx.assert_editor_state(indoc! {"
18551        struct FooRenamedˇ {}
18552    "});
18553}
18554
18555#[gpui::test]
18556async fn test_rename_without_prepare(cx: &mut TestAppContext) {
18557    init_test(cx, |_| {});
18558    // These capabilities indicate that the server does not support prepare rename.
18559    let capabilities = lsp::ServerCapabilities {
18560        rename_provider: Some(lsp::OneOf::Left(true)),
18561        ..Default::default()
18562    };
18563    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
18564
18565    cx.set_state(indoc! {"
18566        struct Fˇoo {}
18567    "});
18568
18569    cx.update_editor(|editor, _window, cx| {
18570        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
18571        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
18572        editor.highlight_background::<DocumentHighlightRead>(
18573            &[highlight_range],
18574            |c| c.editor_document_highlight_read_background,
18575            cx,
18576        );
18577    });
18578
18579    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
18580        .expect("Prepare rename was not started")
18581        .await
18582        .expect("Prepare rename failed");
18583
18584    let mut rename_handler =
18585        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
18586            let edit = lsp::TextEdit {
18587                range: lsp::Range {
18588                    start: lsp::Position {
18589                        line: 0,
18590                        character: 7,
18591                    },
18592                    end: lsp::Position {
18593                        line: 0,
18594                        character: 10,
18595                    },
18596                },
18597                new_text: "FooRenamed".to_string(),
18598            };
18599            Ok(Some(lsp::WorkspaceEdit::new(
18600                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
18601            )))
18602        });
18603    let rename_task = cx
18604        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
18605        .expect("Confirm rename was not started");
18606    rename_handler.next().await.unwrap();
18607    rename_task.await.expect("Confirm rename failed");
18608    cx.run_until_parked();
18609
18610    // Correct range is renamed, as `surrounding_word` is used to find it.
18611    cx.assert_editor_state(indoc! {"
18612        struct FooRenamedˇ {}
18613    "});
18614}
18615
18616#[gpui::test]
18617async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
18618    init_test(cx, |_| {});
18619    let mut cx = EditorTestContext::new(cx).await;
18620
18621    let language = Arc::new(
18622        Language::new(
18623            LanguageConfig::default(),
18624            Some(tree_sitter_html::LANGUAGE.into()),
18625        )
18626        .with_brackets_query(
18627            r#"
18628            ("<" @open "/>" @close)
18629            ("</" @open ">" @close)
18630            ("<" @open ">" @close)
18631            ("\"" @open "\"" @close)
18632            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
18633        "#,
18634        )
18635        .unwrap(),
18636    );
18637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18638
18639    cx.set_state(indoc! {"
18640        <span>ˇ</span>
18641    "});
18642    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18643    cx.assert_editor_state(indoc! {"
18644        <span>
18645        ˇ
18646        </span>
18647    "});
18648
18649    cx.set_state(indoc! {"
18650        <span><span></span>ˇ</span>
18651    "});
18652    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18653    cx.assert_editor_state(indoc! {"
18654        <span><span></span>
18655        ˇ</span>
18656    "});
18657
18658    cx.set_state(indoc! {"
18659        <span>ˇ
18660        </span>
18661    "});
18662    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
18663    cx.assert_editor_state(indoc! {"
18664        <span>
18665        ˇ
18666        </span>
18667    "});
18668}
18669
18670#[gpui::test(iterations = 10)]
18671async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
18672    init_test(cx, |_| {});
18673
18674    let fs = FakeFs::new(cx.executor());
18675    fs.insert_tree(
18676        path!("/dir"),
18677        json!({
18678            "a.ts": "a",
18679        }),
18680    )
18681    .await;
18682
18683    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
18684    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18685    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18686
18687    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18688    language_registry.add(Arc::new(Language::new(
18689        LanguageConfig {
18690            name: "TypeScript".into(),
18691            matcher: LanguageMatcher {
18692                path_suffixes: vec!["ts".to_string()],
18693                ..Default::default()
18694            },
18695            ..Default::default()
18696        },
18697        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18698    )));
18699    let mut fake_language_servers = language_registry.register_fake_lsp(
18700        "TypeScript",
18701        FakeLspAdapter {
18702            capabilities: lsp::ServerCapabilities {
18703                code_lens_provider: Some(lsp::CodeLensOptions {
18704                    resolve_provider: Some(true),
18705                }),
18706                execute_command_provider: Some(lsp::ExecuteCommandOptions {
18707                    commands: vec!["_the/command".to_string()],
18708                    ..lsp::ExecuteCommandOptions::default()
18709                }),
18710                ..lsp::ServerCapabilities::default()
18711            },
18712            ..FakeLspAdapter::default()
18713        },
18714    );
18715
18716    let (buffer, _handle) = project
18717        .update(cx, |p, cx| {
18718            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
18719        })
18720        .await
18721        .unwrap();
18722    cx.executor().run_until_parked();
18723
18724    let fake_server = fake_language_servers.next().await.unwrap();
18725
18726    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
18727    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
18728    drop(buffer_snapshot);
18729    let actions = cx
18730        .update_window(*workspace, |_, window, cx| {
18731            project.code_actions(&buffer, anchor..anchor, window, cx)
18732        })
18733        .unwrap();
18734
18735    fake_server
18736        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
18737            Ok(Some(vec![
18738                lsp::CodeLens {
18739                    range: lsp::Range::default(),
18740                    command: Some(lsp::Command {
18741                        title: "Code lens command".to_owned(),
18742                        command: "_the/command".to_owned(),
18743                        arguments: None,
18744                    }),
18745                    data: None,
18746                },
18747                lsp::CodeLens {
18748                    range: lsp::Range::default(),
18749                    command: Some(lsp::Command {
18750                        title: "Command not in capabilities".to_owned(),
18751                        command: "not in capabilities".to_owned(),
18752                        arguments: None,
18753                    }),
18754                    data: None,
18755                },
18756                lsp::CodeLens {
18757                    range: lsp::Range {
18758                        start: lsp::Position {
18759                            line: 1,
18760                            character: 1,
18761                        },
18762                        end: lsp::Position {
18763                            line: 1,
18764                            character: 1,
18765                        },
18766                    },
18767                    command: Some(lsp::Command {
18768                        title: "Command not in range".to_owned(),
18769                        command: "_the/command".to_owned(),
18770                        arguments: None,
18771                    }),
18772                    data: None,
18773                },
18774            ]))
18775        })
18776        .next()
18777        .await;
18778
18779    let actions = actions.await.unwrap();
18780    assert_eq!(
18781        actions.len(),
18782        1,
18783        "Should have only one valid action for the 0..0 range"
18784    );
18785    let action = actions[0].clone();
18786    let apply = project.update(cx, |project, cx| {
18787        project.apply_code_action(buffer.clone(), action, true, cx)
18788    });
18789
18790    // Resolving the code action does not populate its edits. In absence of
18791    // edits, we must execute the given command.
18792    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
18793        |mut lens, _| async move {
18794            let lens_command = lens.command.as_mut().expect("should have a command");
18795            assert_eq!(lens_command.title, "Code lens command");
18796            lens_command.arguments = Some(vec![json!("the-argument")]);
18797            Ok(lens)
18798        },
18799    );
18800
18801    // While executing the command, the language server sends the editor
18802    // a `workspaceEdit` request.
18803    fake_server
18804        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
18805            let fake = fake_server.clone();
18806            move |params, _| {
18807                assert_eq!(params.command, "_the/command");
18808                let fake = fake.clone();
18809                async move {
18810                    fake.server
18811                        .request::<lsp::request::ApplyWorkspaceEdit>(
18812                            lsp::ApplyWorkspaceEditParams {
18813                                label: None,
18814                                edit: lsp::WorkspaceEdit {
18815                                    changes: Some(
18816                                        [(
18817                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
18818                                            vec![lsp::TextEdit {
18819                                                range: lsp::Range::new(
18820                                                    lsp::Position::new(0, 0),
18821                                                    lsp::Position::new(0, 0),
18822                                                ),
18823                                                new_text: "X".into(),
18824                                            }],
18825                                        )]
18826                                        .into_iter()
18827                                        .collect(),
18828                                    ),
18829                                    ..Default::default()
18830                                },
18831                            },
18832                        )
18833                        .await
18834                        .unwrap();
18835                    Ok(Some(json!(null)))
18836                }
18837            }
18838        })
18839        .next()
18840        .await;
18841
18842    // Applying the code lens command returns a project transaction containing the edits
18843    // sent by the language server in its `workspaceEdit` request.
18844    let transaction = apply.await.unwrap();
18845    assert!(transaction.0.contains_key(&buffer));
18846    buffer.update(cx, |buffer, cx| {
18847        assert_eq!(buffer.text(), "Xa");
18848        buffer.undo(cx);
18849        assert_eq!(buffer.text(), "a");
18850    });
18851}
18852
18853#[gpui::test]
18854async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
18855    init_test(cx, |_| {});
18856
18857    let fs = FakeFs::new(cx.executor());
18858    let main_text = r#"fn main() {
18859println!("1");
18860println!("2");
18861println!("3");
18862println!("4");
18863println!("5");
18864}"#;
18865    let lib_text = "mod foo {}";
18866    fs.insert_tree(
18867        path!("/a"),
18868        json!({
18869            "lib.rs": lib_text,
18870            "main.rs": main_text,
18871        }),
18872    )
18873    .await;
18874
18875    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18876    let (workspace, cx) =
18877        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18878    let worktree_id = workspace.update(cx, |workspace, cx| {
18879        workspace.project().update(cx, |project, cx| {
18880            project.worktrees(cx).next().unwrap().read(cx).id()
18881        })
18882    });
18883
18884    let expected_ranges = vec![
18885        Point::new(0, 0)..Point::new(0, 0),
18886        Point::new(1, 0)..Point::new(1, 1),
18887        Point::new(2, 0)..Point::new(2, 2),
18888        Point::new(3, 0)..Point::new(3, 3),
18889    ];
18890
18891    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
18892    let editor_1 = workspace
18893        .update_in(cx, |workspace, window, cx| {
18894            workspace.open_path(
18895                (worktree_id, "main.rs"),
18896                Some(pane_1.downgrade()),
18897                true,
18898                window,
18899                cx,
18900            )
18901        })
18902        .unwrap()
18903        .await
18904        .downcast::<Editor>()
18905        .unwrap();
18906    pane_1.update(cx, |pane, cx| {
18907        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18908        open_editor.update(cx, |editor, cx| {
18909            assert_eq!(
18910                editor.display_text(cx),
18911                main_text,
18912                "Original main.rs text on initial open",
18913            );
18914            assert_eq!(
18915                editor
18916                    .selections
18917                    .all::<Point>(cx)
18918                    .into_iter()
18919                    .map(|s| s.range())
18920                    .collect::<Vec<_>>(),
18921                vec![Point::zero()..Point::zero()],
18922                "Default selections on initial open",
18923            );
18924        })
18925    });
18926    editor_1.update_in(cx, |editor, window, cx| {
18927        editor.change_selections(None, window, cx, |s| {
18928            s.select_ranges(expected_ranges.clone());
18929        });
18930    });
18931
18932    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
18933        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
18934    });
18935    let editor_2 = workspace
18936        .update_in(cx, |workspace, window, cx| {
18937            workspace.open_path(
18938                (worktree_id, "main.rs"),
18939                Some(pane_2.downgrade()),
18940                true,
18941                window,
18942                cx,
18943            )
18944        })
18945        .unwrap()
18946        .await
18947        .downcast::<Editor>()
18948        .unwrap();
18949    pane_2.update(cx, |pane, cx| {
18950        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
18951        open_editor.update(cx, |editor, cx| {
18952            assert_eq!(
18953                editor.display_text(cx),
18954                main_text,
18955                "Original main.rs text on initial open in another panel",
18956            );
18957            assert_eq!(
18958                editor
18959                    .selections
18960                    .all::<Point>(cx)
18961                    .into_iter()
18962                    .map(|s| s.range())
18963                    .collect::<Vec<_>>(),
18964                vec![Point::zero()..Point::zero()],
18965                "Default selections on initial open in another panel",
18966            );
18967        })
18968    });
18969
18970    editor_2.update_in(cx, |editor, window, cx| {
18971        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
18972    });
18973
18974    let _other_editor_1 = workspace
18975        .update_in(cx, |workspace, window, cx| {
18976            workspace.open_path(
18977                (worktree_id, "lib.rs"),
18978                Some(pane_1.downgrade()),
18979                true,
18980                window,
18981                cx,
18982            )
18983        })
18984        .unwrap()
18985        .await
18986        .downcast::<Editor>()
18987        .unwrap();
18988    pane_1
18989        .update_in(cx, |pane, window, cx| {
18990            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
18991                .unwrap()
18992        })
18993        .await
18994        .unwrap();
18995    drop(editor_1);
18996    pane_1.update(cx, |pane, cx| {
18997        pane.active_item()
18998            .unwrap()
18999            .downcast::<Editor>()
19000            .unwrap()
19001            .update(cx, |editor, cx| {
19002                assert_eq!(
19003                    editor.display_text(cx),
19004                    lib_text,
19005                    "Other file should be open and active",
19006                );
19007            });
19008        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19009    });
19010
19011    let _other_editor_2 = workspace
19012        .update_in(cx, |workspace, window, cx| {
19013            workspace.open_path(
19014                (worktree_id, "lib.rs"),
19015                Some(pane_2.downgrade()),
19016                true,
19017                window,
19018                cx,
19019            )
19020        })
19021        .unwrap()
19022        .await
19023        .downcast::<Editor>()
19024        .unwrap();
19025    pane_2
19026        .update_in(cx, |pane, window, cx| {
19027            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19028                .unwrap()
19029        })
19030        .await
19031        .unwrap();
19032    drop(editor_2);
19033    pane_2.update(cx, |pane, cx| {
19034        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19035        open_editor.update(cx, |editor, cx| {
19036            assert_eq!(
19037                editor.display_text(cx),
19038                lib_text,
19039                "Other file should be open and active in another panel too",
19040            );
19041        });
19042        assert_eq!(
19043            pane.items().count(),
19044            1,
19045            "No other editors should be open in another pane",
19046        );
19047    });
19048
19049    let _editor_1_reopened = workspace
19050        .update_in(cx, |workspace, window, cx| {
19051            workspace.open_path(
19052                (worktree_id, "main.rs"),
19053                Some(pane_1.downgrade()),
19054                true,
19055                window,
19056                cx,
19057            )
19058        })
19059        .unwrap()
19060        .await
19061        .downcast::<Editor>()
19062        .unwrap();
19063    let _editor_2_reopened = workspace
19064        .update_in(cx, |workspace, window, cx| {
19065            workspace.open_path(
19066                (worktree_id, "main.rs"),
19067                Some(pane_2.downgrade()),
19068                true,
19069                window,
19070                cx,
19071            )
19072        })
19073        .unwrap()
19074        .await
19075        .downcast::<Editor>()
19076        .unwrap();
19077    pane_1.update(cx, |pane, cx| {
19078        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19079        open_editor.update(cx, |editor, cx| {
19080            assert_eq!(
19081                editor.display_text(cx),
19082                main_text,
19083                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19084            );
19085            assert_eq!(
19086                editor
19087                    .selections
19088                    .all::<Point>(cx)
19089                    .into_iter()
19090                    .map(|s| s.range())
19091                    .collect::<Vec<_>>(),
19092                expected_ranges,
19093                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19094            );
19095        })
19096    });
19097    pane_2.update(cx, |pane, cx| {
19098        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19099        open_editor.update(cx, |editor, cx| {
19100            assert_eq!(
19101                editor.display_text(cx),
19102                r#"fn main() {
19103⋯rintln!("1");
19104⋯intln!("2");
19105⋯ntln!("3");
19106println!("4");
19107println!("5");
19108}"#,
19109                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19110            );
19111            assert_eq!(
19112                editor
19113                    .selections
19114                    .all::<Point>(cx)
19115                    .into_iter()
19116                    .map(|s| s.range())
19117                    .collect::<Vec<_>>(),
19118                vec![Point::zero()..Point::zero()],
19119                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19120            );
19121        })
19122    });
19123}
19124
19125#[gpui::test]
19126async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19127    init_test(cx, |_| {});
19128
19129    let fs = FakeFs::new(cx.executor());
19130    let main_text = r#"fn main() {
19131println!("1");
19132println!("2");
19133println!("3");
19134println!("4");
19135println!("5");
19136}"#;
19137    let lib_text = "mod foo {}";
19138    fs.insert_tree(
19139        path!("/a"),
19140        json!({
19141            "lib.rs": lib_text,
19142            "main.rs": main_text,
19143        }),
19144    )
19145    .await;
19146
19147    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19148    let (workspace, cx) =
19149        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19150    let worktree_id = workspace.update(cx, |workspace, cx| {
19151        workspace.project().update(cx, |project, cx| {
19152            project.worktrees(cx).next().unwrap().read(cx).id()
19153        })
19154    });
19155
19156    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19157    let editor = workspace
19158        .update_in(cx, |workspace, window, cx| {
19159            workspace.open_path(
19160                (worktree_id, "main.rs"),
19161                Some(pane.downgrade()),
19162                true,
19163                window,
19164                cx,
19165            )
19166        })
19167        .unwrap()
19168        .await
19169        .downcast::<Editor>()
19170        .unwrap();
19171    pane.update(cx, |pane, cx| {
19172        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19173        open_editor.update(cx, |editor, cx| {
19174            assert_eq!(
19175                editor.display_text(cx),
19176                main_text,
19177                "Original main.rs text on initial open",
19178            );
19179        })
19180    });
19181    editor.update_in(cx, |editor, window, cx| {
19182        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19183    });
19184
19185    cx.update_global(|store: &mut SettingsStore, cx| {
19186        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19187            s.restore_on_file_reopen = Some(false);
19188        });
19189    });
19190    editor.update_in(cx, |editor, window, cx| {
19191        editor.fold_ranges(
19192            vec![
19193                Point::new(1, 0)..Point::new(1, 1),
19194                Point::new(2, 0)..Point::new(2, 2),
19195                Point::new(3, 0)..Point::new(3, 3),
19196            ],
19197            false,
19198            window,
19199            cx,
19200        );
19201    });
19202    pane.update_in(cx, |pane, window, cx| {
19203        pane.close_all_items(&CloseAllItems::default(), window, cx)
19204            .unwrap()
19205    })
19206    .await
19207    .unwrap();
19208    pane.update(cx, |pane, _| {
19209        assert!(pane.active_item().is_none());
19210    });
19211    cx.update_global(|store: &mut SettingsStore, cx| {
19212        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19213            s.restore_on_file_reopen = Some(true);
19214        });
19215    });
19216
19217    let _editor_reopened = workspace
19218        .update_in(cx, |workspace, window, cx| {
19219            workspace.open_path(
19220                (worktree_id, "main.rs"),
19221                Some(pane.downgrade()),
19222                true,
19223                window,
19224                cx,
19225            )
19226        })
19227        .unwrap()
19228        .await
19229        .downcast::<Editor>()
19230        .unwrap();
19231    pane.update(cx, |pane, cx| {
19232        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19233        open_editor.update(cx, |editor, cx| {
19234            assert_eq!(
19235                editor.display_text(cx),
19236                main_text,
19237                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19238            );
19239        })
19240    });
19241}
19242
19243fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
19244    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
19245    point..point
19246}
19247
19248fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
19249    let (text, ranges) = marked_text_ranges(marked_text, true);
19250    assert_eq!(editor.text(cx), text);
19251    assert_eq!(
19252        editor.selections.ranges(cx),
19253        ranges,
19254        "Assert selections are {}",
19255        marked_text
19256    );
19257}
19258
19259pub fn handle_signature_help_request(
19260    cx: &mut EditorLspTestContext,
19261    mocked_response: lsp::SignatureHelp,
19262) -> impl Future<Output = ()> + use<> {
19263    let mut request =
19264        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
19265            let mocked_response = mocked_response.clone();
19266            async move { Ok(Some(mocked_response)) }
19267        });
19268
19269    async move {
19270        request.next().await;
19271    }
19272}
19273
19274/// Handle completion request passing a marked string specifying where the completion
19275/// should be triggered from using '|' character, what range should be replaced, and what completions
19276/// should be returned using '<' and '>' to delimit the range.
19277///
19278/// Also see `handle_completion_request_with_insert_and_replace`.
19279#[track_caller]
19280pub fn handle_completion_request(
19281    cx: &mut EditorLspTestContext,
19282    marked_string: &str,
19283    completions: Vec<&'static str>,
19284    counter: Arc<AtomicUsize>,
19285) -> impl Future<Output = ()> {
19286    let complete_from_marker: TextRangeMarker = '|'.into();
19287    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19288    let (_, mut marked_ranges) = marked_text_ranges_by(
19289        marked_string,
19290        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19291    );
19292
19293    let complete_from_position =
19294        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19295    let replace_range =
19296        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19297
19298    let mut request =
19299        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19300            let completions = completions.clone();
19301            counter.fetch_add(1, atomic::Ordering::Release);
19302            async move {
19303                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19304                assert_eq!(
19305                    params.text_document_position.position,
19306                    complete_from_position
19307                );
19308                Ok(Some(lsp::CompletionResponse::Array(
19309                    completions
19310                        .iter()
19311                        .map(|completion_text| lsp::CompletionItem {
19312                            label: completion_text.to_string(),
19313                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19314                                range: replace_range,
19315                                new_text: completion_text.to_string(),
19316                            })),
19317                            ..Default::default()
19318                        })
19319                        .collect(),
19320                )))
19321            }
19322        });
19323
19324    async move {
19325        request.next().await;
19326    }
19327}
19328
19329/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
19330/// given instead, which also contains an `insert` range.
19331///
19332/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
19333/// that is, `replace_range.start..cursor_pos`.
19334pub fn handle_completion_request_with_insert_and_replace(
19335    cx: &mut EditorLspTestContext,
19336    marked_string: &str,
19337    completions: Vec<&'static str>,
19338    counter: Arc<AtomicUsize>,
19339) -> impl Future<Output = ()> {
19340    let complete_from_marker: TextRangeMarker = '|'.into();
19341    let replace_range_marker: TextRangeMarker = ('<', '>').into();
19342    let (_, mut marked_ranges) = marked_text_ranges_by(
19343        marked_string,
19344        vec![complete_from_marker.clone(), replace_range_marker.clone()],
19345    );
19346
19347    let complete_from_position =
19348        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
19349    let replace_range =
19350        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
19351
19352    let mut request =
19353        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
19354            let completions = completions.clone();
19355            counter.fetch_add(1, atomic::Ordering::Release);
19356            async move {
19357                assert_eq!(params.text_document_position.text_document.uri, url.clone());
19358                assert_eq!(
19359                    params.text_document_position.position, complete_from_position,
19360                    "marker `|` position doesn't match",
19361                );
19362                Ok(Some(lsp::CompletionResponse::Array(
19363                    completions
19364                        .iter()
19365                        .map(|completion_text| lsp::CompletionItem {
19366                            label: completion_text.to_string(),
19367                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19368                                lsp::InsertReplaceEdit {
19369                                    insert: lsp::Range {
19370                                        start: replace_range.start,
19371                                        end: complete_from_position,
19372                                    },
19373                                    replace: replace_range,
19374                                    new_text: completion_text.to_string(),
19375                                },
19376                            )),
19377                            ..Default::default()
19378                        })
19379                        .collect(),
19380                )))
19381            }
19382        });
19383
19384    async move {
19385        request.next().await;
19386    }
19387}
19388
19389fn handle_resolve_completion_request(
19390    cx: &mut EditorLspTestContext,
19391    edits: Option<Vec<(&'static str, &'static str)>>,
19392) -> impl Future<Output = ()> {
19393    let edits = edits.map(|edits| {
19394        edits
19395            .iter()
19396            .map(|(marked_string, new_text)| {
19397                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
19398                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
19399                lsp::TextEdit::new(replace_range, new_text.to_string())
19400            })
19401            .collect::<Vec<_>>()
19402    });
19403
19404    let mut request =
19405        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19406            let edits = edits.clone();
19407            async move {
19408                Ok(lsp::CompletionItem {
19409                    additional_text_edits: edits,
19410                    ..Default::default()
19411                })
19412            }
19413        });
19414
19415    async move {
19416        request.next().await;
19417    }
19418}
19419
19420pub(crate) fn update_test_language_settings(
19421    cx: &mut TestAppContext,
19422    f: impl Fn(&mut AllLanguageSettingsContent),
19423) {
19424    cx.update(|cx| {
19425        SettingsStore::update_global(cx, |store, cx| {
19426            store.update_user_settings::<AllLanguageSettings>(cx, f);
19427        });
19428    });
19429}
19430
19431pub(crate) fn update_test_project_settings(
19432    cx: &mut TestAppContext,
19433    f: impl Fn(&mut ProjectSettings),
19434) {
19435    cx.update(|cx| {
19436        SettingsStore::update_global(cx, |store, cx| {
19437            store.update_user_settings::<ProjectSettings>(cx, f);
19438        });
19439    });
19440}
19441
19442pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
19443    cx.update(|cx| {
19444        assets::Assets.load_test_fonts(cx);
19445        let store = SettingsStore::test(cx);
19446        cx.set_global(store);
19447        theme::init(theme::LoadThemes::JustBase, cx);
19448        release_channel::init(SemanticVersion::default(), cx);
19449        client::init_settings(cx);
19450        language::init(cx);
19451        Project::init_settings(cx);
19452        workspace::init_settings(cx);
19453        crate::init(cx);
19454    });
19455
19456    update_test_language_settings(cx, f);
19457}
19458
19459#[track_caller]
19460fn assert_hunk_revert(
19461    not_reverted_text_with_selections: &str,
19462    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
19463    expected_reverted_text_with_selections: &str,
19464    base_text: &str,
19465    cx: &mut EditorLspTestContext,
19466) {
19467    cx.set_state(not_reverted_text_with_selections);
19468    cx.set_head_text(base_text);
19469    cx.executor().run_until_parked();
19470
19471    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
19472        let snapshot = editor.snapshot(window, cx);
19473        let reverted_hunk_statuses = snapshot
19474            .buffer_snapshot
19475            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
19476            .map(|hunk| hunk.status().kind)
19477            .collect::<Vec<_>>();
19478
19479        editor.git_restore(&Default::default(), window, cx);
19480        reverted_hunk_statuses
19481    });
19482    cx.executor().run_until_parked();
19483    cx.assert_editor_state(expected_reverted_text_with_selections);
19484    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
19485}