editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    linked_editing_ranges::LinkedEditingRanges,
    5    scroll::scroll_amount::ScrollAmount,
    6    test::{
    7        assert_text_with_selections, build_editor,
    8        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
    9        editor_test_context::EditorTestContext,
   10        select_ranges,
   11    },
   12};
   13use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   14use futures::StreamExt;
   15use gpui::{
   16    BackgroundExecutor, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
   17    VisualTestContext, WindowBounds, WindowOptions, div,
   18};
   19use indoc::indoc;
   20use language::{
   21    BracketPairConfig,
   22    Capability::ReadWrite,
   23    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   24    Override, Point,
   25    language_settings::{
   26        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   27        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   28    },
   29};
   30use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   31use lsp::CompletionParams;
   32use multi_buffer::{IndentGuide, PathKey};
   33use parking_lot::Mutex;
   34use pretty_assertions::{assert_eq, assert_ne};
   35use project::{
   36    FakeFs,
   37    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   38    project_settings::{LspSettings, ProjectSettings},
   39};
   40use serde_json::{self, json};
   41use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   42use std::{
   43    iter,
   44    sync::atomic::{self, AtomicUsize},
   45};
   46use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   47use text::ToPoint as _;
   48use unindent::Unindent;
   49use util::{
   50    assert_set_eq, path,
   51    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   52    uri,
   53};
   54use workspace::{
   55    CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
   56    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   57};
   58
   59#[gpui::test]
   60fn test_edit_events(cx: &mut TestAppContext) {
   61    init_test(cx, |_| {});
   62
   63    let buffer = cx.new(|cx| {
   64        let mut buffer = language::Buffer::local("123456", cx);
   65        buffer.set_group_interval(Duration::from_secs(1));
   66        buffer
   67    });
   68
   69    let events = Rc::new(RefCell::new(Vec::new()));
   70    let editor1 = cx.add_window({
   71        let events = events.clone();
   72        |window, cx| {
   73            let entity = cx.entity().clone();
   74            cx.subscribe_in(
   75                &entity,
   76                window,
   77                move |_, _, event: &EditorEvent, _, _| match event {
   78                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   79                    EditorEvent::BufferEdited => {
   80                        events.borrow_mut().push(("editor1", "buffer edited"))
   81                    }
   82                    _ => {}
   83                },
   84            )
   85            .detach();
   86            Editor::for_buffer(buffer.clone(), None, window, cx)
   87        }
   88    });
   89
   90    let editor2 = cx.add_window({
   91        let events = events.clone();
   92        |window, cx| {
   93            cx.subscribe_in(
   94                &cx.entity().clone(),
   95                window,
   96                move |_, _, event: &EditorEvent, _, _| match event {
   97                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
   98                    EditorEvent::BufferEdited => {
   99                        events.borrow_mut().push(("editor2", "buffer edited"))
  100                    }
  101                    _ => {}
  102                },
  103            )
  104            .detach();
  105            Editor::for_buffer(buffer.clone(), None, window, cx)
  106        }
  107    });
  108
  109    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  110
  111    // Mutating editor 1 will emit an `Edited` event only for that editor.
  112    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  113    assert_eq!(
  114        mem::take(&mut *events.borrow_mut()),
  115        [
  116            ("editor1", "edited"),
  117            ("editor1", "buffer edited"),
  118            ("editor2", "buffer edited"),
  119        ]
  120    );
  121
  122    // Mutating editor 2 will emit an `Edited` event only for that editor.
  123    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  124    assert_eq!(
  125        mem::take(&mut *events.borrow_mut()),
  126        [
  127            ("editor2", "edited"),
  128            ("editor1", "buffer edited"),
  129            ("editor2", "buffer edited"),
  130        ]
  131    );
  132
  133    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  134    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor1", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  145    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor1", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  156    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor2", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  167    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor2", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // No event is emitted when the mutation is a no-op.
  178    _ = editor2.update(cx, |editor, window, cx| {
  179        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  180
  181        editor.backspace(&Backspace, window, cx);
  182    });
  183    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  184}
  185
  186#[gpui::test]
  187fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  188    init_test(cx, |_| {});
  189
  190    let mut now = Instant::now();
  191    let group_interval = Duration::from_millis(1);
  192    let buffer = cx.new(|cx| {
  193        let mut buf = language::Buffer::local("123456", cx);
  194        buf.set_group_interval(group_interval);
  195        buf
  196    });
  197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  198    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  199
  200    _ = editor.update(cx, |editor, window, cx| {
  201        editor.start_transaction_at(now, window, cx);
  202        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  203
  204        editor.insert("cd", window, cx);
  205        editor.end_transaction_at(now, cx);
  206        assert_eq!(editor.text(cx), "12cd56");
  207        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  208
  209        editor.start_transaction_at(now, window, cx);
  210        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  211        editor.insert("e", window, cx);
  212        editor.end_transaction_at(now, cx);
  213        assert_eq!(editor.text(cx), "12cde6");
  214        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  215
  216        now += group_interval + Duration::from_millis(1);
  217        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  218
  219        // Simulate an edit in another editor
  220        buffer.update(cx, |buffer, cx| {
  221            buffer.start_transaction_at(now, cx);
  222            buffer.edit([(0..1, "a")], None, cx);
  223            buffer.edit([(1..1, "b")], None, cx);
  224            buffer.end_transaction_at(now, cx);
  225        });
  226
  227        assert_eq!(editor.text(cx), "ab2cde6");
  228        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  229
  230        // Last transaction happened past the group interval in a different editor.
  231        // Undo it individually and don't restore selections.
  232        editor.undo(&Undo, window, cx);
  233        assert_eq!(editor.text(cx), "12cde6");
  234        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  235
  236        // First two transactions happened within the group interval in this editor.
  237        // Undo them together and restore selections.
  238        editor.undo(&Undo, window, cx);
  239        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  240        assert_eq!(editor.text(cx), "123456");
  241        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  242
  243        // Redo the first two transactions together.
  244        editor.redo(&Redo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  247
  248        // Redo the last transaction on its own.
  249        editor.redo(&Redo, window, cx);
  250        assert_eq!(editor.text(cx), "ab2cde6");
  251        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  252
  253        // Test empty transactions.
  254        editor.start_transaction_at(now, window, cx);
  255        editor.end_transaction_at(now, cx);
  256        editor.undo(&Undo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258    });
  259}
  260
  261#[gpui::test]
  262fn test_ime_composition(cx: &mut TestAppContext) {
  263    init_test(cx, |_| {});
  264
  265    let buffer = cx.new(|cx| {
  266        let mut buffer = language::Buffer::local("abcde", cx);
  267        // Ensure automatic grouping doesn't occur.
  268        buffer.set_group_interval(Duration::ZERO);
  269        buffer
  270    });
  271
  272    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  273    cx.add_window(|window, cx| {
  274        let mut editor = build_editor(buffer.clone(), window, cx);
  275
  276        // Start a new IME composition.
  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        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  280        assert_eq!(editor.text(cx), "äbcde");
  281        assert_eq!(
  282            editor.marked_text_ranges(cx),
  283            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  284        );
  285
  286        // Finalize IME composition.
  287        editor.replace_text_in_range(None, "ā", window, cx);
  288        assert_eq!(editor.text(cx), "ābcde");
  289        assert_eq!(editor.marked_text_ranges(cx), None);
  290
  291        // IME composition edits are grouped and are undone/redone at once.
  292        editor.undo(&Default::default(), window, cx);
  293        assert_eq!(editor.text(cx), "abcde");
  294        assert_eq!(editor.marked_text_ranges(cx), None);
  295        editor.redo(&Default::default(), window, cx);
  296        assert_eq!(editor.text(cx), "ābcde");
  297        assert_eq!(editor.marked_text_ranges(cx), None);
  298
  299        // Start a new IME composition.
  300        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Undoing during an IME composition cancels it.
  307        editor.undo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  312        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  313        assert_eq!(editor.text(cx), "ābcdè");
  314        assert_eq!(
  315            editor.marked_text_ranges(cx),
  316            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  317        );
  318
  319        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  320        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  321        assert_eq!(editor.text(cx), "ābcdę");
  322        assert_eq!(editor.marked_text_ranges(cx), None);
  323
  324        // Start a new IME composition with multiple cursors.
  325        editor.change_selections(None, window, cx, |s| {
  326            s.select_ranges([
  327                OffsetUtf16(1)..OffsetUtf16(1),
  328                OffsetUtf16(3)..OffsetUtf16(3),
  329                OffsetUtf16(5)..OffsetUtf16(5),
  330            ])
  331        });
  332        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  333        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![
  337                OffsetUtf16(0)..OffsetUtf16(3),
  338                OffsetUtf16(4)..OffsetUtf16(7),
  339                OffsetUtf16(8)..OffsetUtf16(11)
  340            ])
  341        );
  342
  343        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  344        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  345        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(1)..OffsetUtf16(2),
  350                OffsetUtf16(5)..OffsetUtf16(6),
  351                OffsetUtf16(9)..OffsetUtf16(10)
  352            ])
  353        );
  354
  355        // Finalize IME composition with multiple cursors.
  356        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  357        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  358        assert_eq!(editor.marked_text_ranges(cx), None);
  359
  360        editor
  361    });
  362}
  363
  364#[gpui::test]
  365fn test_selection_with_mouse(cx: &mut TestAppContext) {
  366    init_test(cx, |_| {});
  367
  368    let editor = cx.add_window(|window, cx| {
  369        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  370        build_editor(buffer, window, cx)
  371    });
  372
  373    _ = editor.update(cx, |editor, window, cx| {
  374        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  375    });
  376    assert_eq!(
  377        editor
  378            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  379            .unwrap(),
  380        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  381    );
  382
  383    _ = editor.update(cx, |editor, window, cx| {
  384        editor.update_selection(
  385            DisplayPoint::new(DisplayRow(3), 3),
  386            0,
  387            gpui::Point::<f32>::default(),
  388            window,
  389            cx,
  390        );
  391    });
  392
  393    assert_eq!(
  394        editor
  395            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  396            .unwrap(),
  397        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  398    );
  399
  400    _ = editor.update(cx, |editor, window, cx| {
  401        editor.update_selection(
  402            DisplayPoint::new(DisplayRow(1), 1),
  403            0,
  404            gpui::Point::<f32>::default(),
  405            window,
  406            cx,
  407        );
  408    });
  409
  410    assert_eq!(
  411        editor
  412            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  413            .unwrap(),
  414        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  415    );
  416
  417    _ = editor.update(cx, |editor, window, cx| {
  418        editor.end_selection(window, cx);
  419        editor.update_selection(
  420            DisplayPoint::new(DisplayRow(3), 3),
  421            0,
  422            gpui::Point::<f32>::default(),
  423            window,
  424            cx,
  425        );
  426    });
  427
  428    assert_eq!(
  429        editor
  430            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  431            .unwrap(),
  432        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  433    );
  434
  435    _ = editor.update(cx, |editor, window, cx| {
  436        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  437        editor.update_selection(
  438            DisplayPoint::new(DisplayRow(0), 0),
  439            0,
  440            gpui::Point::<f32>::default(),
  441            window,
  442            cx,
  443        );
  444    });
  445
  446    assert_eq!(
  447        editor
  448            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  449            .unwrap(),
  450        [
  451            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  452            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  453        ]
  454    );
  455
  456    _ = editor.update(cx, |editor, window, cx| {
  457        editor.end_selection(window, cx);
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  463            .unwrap(),
  464        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  465    );
  466}
  467
  468#[gpui::test]
  469fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  470    init_test(cx, |_| {});
  471
  472    let editor = cx.add_window(|window, cx| {
  473        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  474        build_editor(buffer, window, cx)
  475    });
  476
  477    _ = editor.update(cx, |editor, window, cx| {
  478        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  479    });
  480
  481    _ = editor.update(cx, |editor, window, cx| {
  482        editor.end_selection(window, cx);
  483    });
  484
  485    _ = editor.update(cx, |editor, window, cx| {
  486        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.end_selection(window, cx);
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  496            .unwrap(),
  497        [
  498            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  499            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  500        ]
  501    );
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  505    });
  506
  507    _ = editor.update(cx, |editor, window, cx| {
  508        editor.end_selection(window, cx);
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  514            .unwrap(),
  515        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  516    );
  517}
  518
  519#[gpui::test]
  520fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  521    init_test(cx, |_| {});
  522
  523    let editor = cx.add_window(|window, cx| {
  524        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  525        build_editor(buffer, window, cx)
  526    });
  527
  528    _ = editor.update(cx, |editor, window, cx| {
  529        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  530        assert_eq!(
  531            editor.selections.display_ranges(cx),
  532            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  533        );
  534    });
  535
  536    _ = editor.update(cx, |editor, window, cx| {
  537        editor.update_selection(
  538            DisplayPoint::new(DisplayRow(3), 3),
  539            0,
  540            gpui::Point::<f32>::default(),
  541            window,
  542            cx,
  543        );
  544        assert_eq!(
  545            editor.selections.display_ranges(cx),
  546            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  547        );
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.cancel(&Cancel, window, cx);
  552        editor.update_selection(
  553            DisplayPoint::new(DisplayRow(1), 1),
  554            0,
  555            gpui::Point::<f32>::default(),
  556            window,
  557            cx,
  558        );
  559        assert_eq!(
  560            editor.selections.display_ranges(cx),
  561            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  562        );
  563    });
  564}
  565
  566#[gpui::test]
  567fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  568    init_test(cx, |_| {});
  569
  570    let editor = cx.add_window(|window, cx| {
  571        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  572        build_editor(buffer, window, cx)
  573    });
  574
  575    _ = editor.update(cx, |editor, window, cx| {
  576        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  577        assert_eq!(
  578            editor.selections.display_ranges(cx),
  579            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  580        );
  581
  582        editor.move_down(&Default::default(), window, cx);
  583        assert_eq!(
  584            editor.selections.display_ranges(cx),
  585            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  586        );
  587
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_up(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  598        );
  599    });
  600}
  601
  602#[gpui::test]
  603fn test_clone(cx: &mut TestAppContext) {
  604    init_test(cx, |_| {});
  605
  606    let (text, selection_ranges) = marked_text_ranges(
  607        indoc! {"
  608            one
  609            two
  610            threeˇ
  611            four
  612            fiveˇ
  613        "},
  614        true,
  615    );
  616
  617    let editor = cx.add_window(|window, cx| {
  618        let buffer = MultiBuffer::build_simple(&text, cx);
  619        build_editor(buffer, window, cx)
  620    });
  621
  622    _ = editor.update(cx, |editor, window, cx| {
  623        editor.change_selections(None, window, cx, |s| {
  624            s.select_ranges(selection_ranges.clone())
  625        });
  626        editor.fold_creases(
  627            vec![
  628                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  629                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  630            ],
  631            true,
  632            window,
  633            cx,
  634        );
  635    });
  636
  637    let cloned_editor = editor
  638        .update(cx, |editor, _, cx| {
  639            cx.open_window(Default::default(), |window, cx| {
  640                cx.new(|cx| editor.clone(window, cx))
  641            })
  642        })
  643        .unwrap()
  644        .unwrap();
  645
  646    let snapshot = editor
  647        .update(cx, |e, window, cx| e.snapshot(window, cx))
  648        .unwrap();
  649    let cloned_snapshot = cloned_editor
  650        .update(cx, |e, window, cx| e.snapshot(window, cx))
  651        .unwrap();
  652
  653    assert_eq!(
  654        cloned_editor
  655            .update(cx, |e, _, cx| e.display_text(cx))
  656            .unwrap(),
  657        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  658    );
  659    assert_eq!(
  660        cloned_snapshot
  661            .folds_in_range(0..text.len())
  662            .collect::<Vec<_>>(),
  663        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  664    );
  665    assert_set_eq!(
  666        cloned_editor
  667            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  668            .unwrap(),
  669        editor
  670            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  671            .unwrap()
  672    );
  673    assert_set_eq!(
  674        cloned_editor
  675            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  676            .unwrap(),
  677        editor
  678            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  679            .unwrap()
  680    );
  681}
  682
  683#[gpui::test]
  684async fn test_navigation_history(cx: &mut TestAppContext) {
  685    init_test(cx, |_| {});
  686
  687    use workspace::item::Item;
  688
  689    let fs = FakeFs::new(cx.executor());
  690    let project = Project::test(fs, [], cx).await;
  691    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  692    let pane = workspace
  693        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  694        .unwrap();
  695
  696    _ = workspace.update(cx, |_v, window, cx| {
  697        cx.new(|cx| {
  698            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  699            let mut editor = build_editor(buffer.clone(), window, cx);
  700            let handle = cx.entity();
  701            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  702
  703            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  704                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  705            }
  706
  707            // Move the cursor a small distance.
  708            // Nothing is added to the navigation history.
  709            editor.change_selections(None, window, cx, |s| {
  710                s.select_display_ranges([
  711                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  712                ])
  713            });
  714            editor.change_selections(None, window, cx, |s| {
  715                s.select_display_ranges([
  716                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  717                ])
  718            });
  719            assert!(pop_history(&mut editor, cx).is_none());
  720
  721            // Move the cursor a large distance.
  722            // The history can jump back to the previous position.
  723            editor.change_selections(None, window, cx, |s| {
  724                s.select_display_ranges([
  725                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  726                ])
  727            });
  728            let nav_entry = pop_history(&mut editor, cx).unwrap();
  729            editor.navigate(nav_entry.data.unwrap(), window, cx);
  730            assert_eq!(nav_entry.item.id(), cx.entity_id());
  731            assert_eq!(
  732                editor.selections.display_ranges(cx),
  733                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  734            );
  735            assert!(pop_history(&mut editor, cx).is_none());
  736
  737            // Move the cursor a small distance via the mouse.
  738            // Nothing is added to the navigation history.
  739            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  740            editor.end_selection(window, cx);
  741            assert_eq!(
  742                editor.selections.display_ranges(cx),
  743                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  744            );
  745            assert!(pop_history(&mut editor, cx).is_none());
  746
  747            // Move the cursor a large distance via the mouse.
  748            // The history can jump back to the previous position.
  749            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  750            editor.end_selection(window, cx);
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  754            );
  755            let nav_entry = pop_history(&mut editor, cx).unwrap();
  756            editor.navigate(nav_entry.data.unwrap(), window, cx);
  757            assert_eq!(nav_entry.item.id(), cx.entity_id());
  758            assert_eq!(
  759                editor.selections.display_ranges(cx),
  760                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  761            );
  762            assert!(pop_history(&mut editor, cx).is_none());
  763
  764            // Set scroll position to check later
  765            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  766            let original_scroll_position = editor.scroll_manager.anchor();
  767
  768            // Jump to the end of the document and adjust scroll
  769            editor.move_to_end(&MoveToEnd, window, cx);
  770            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  771            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  772
  773            let nav_entry = pop_history(&mut editor, cx).unwrap();
  774            editor.navigate(nav_entry.data.unwrap(), window, cx);
  775            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  776
  777            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  778            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  779            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  780            let invalid_point = Point::new(9999, 0);
  781            editor.navigate(
  782                Box::new(NavigationData {
  783                    cursor_anchor: invalid_anchor,
  784                    cursor_position: invalid_point,
  785                    scroll_anchor: ScrollAnchor {
  786                        anchor: invalid_anchor,
  787                        offset: Default::default(),
  788                    },
  789                    scroll_top_row: invalid_point.row,
  790                }),
  791                window,
  792                cx,
  793            );
  794            assert_eq!(
  795                editor.selections.display_ranges(cx),
  796                &[editor.max_point(cx)..editor.max_point(cx)]
  797            );
  798            assert_eq!(
  799                editor.scroll_position(cx),
  800                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  801            );
  802
  803            editor
  804        })
  805    });
  806}
  807
  808#[gpui::test]
  809fn test_cancel(cx: &mut TestAppContext) {
  810    init_test(cx, |_| {});
  811
  812    let editor = cx.add_window(|window, cx| {
  813        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  814        build_editor(buffer, window, cx)
  815    });
  816
  817    _ = editor.update(cx, |editor, window, cx| {
  818        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  819        editor.update_selection(
  820            DisplayPoint::new(DisplayRow(1), 1),
  821            0,
  822            gpui::Point::<f32>::default(),
  823            window,
  824            cx,
  825        );
  826        editor.end_selection(window, cx);
  827
  828        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  829        editor.update_selection(
  830            DisplayPoint::new(DisplayRow(0), 3),
  831            0,
  832            gpui::Point::<f32>::default(),
  833            window,
  834            cx,
  835        );
  836        editor.end_selection(window, cx);
  837        assert_eq!(
  838            editor.selections.display_ranges(cx),
  839            [
  840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  841                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  842            ]
  843        );
  844    });
  845
  846    _ = editor.update(cx, |editor, window, cx| {
  847        editor.cancel(&Cancel, window, cx);
  848        assert_eq!(
  849            editor.selections.display_ranges(cx),
  850            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  851        );
  852    });
  853
  854    _ = editor.update(cx, |editor, window, cx| {
  855        editor.cancel(&Cancel, window, cx);
  856        assert_eq!(
  857            editor.selections.display_ranges(cx),
  858            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  859        );
  860    });
  861}
  862
  863#[gpui::test]
  864fn test_fold_action(cx: &mut TestAppContext) {
  865    init_test(cx, |_| {});
  866
  867    let editor = cx.add_window(|window, cx| {
  868        let buffer = MultiBuffer::build_simple(
  869            &"
  870                impl Foo {
  871                    // Hello!
  872
  873                    fn a() {
  874                        1
  875                    }
  876
  877                    fn b() {
  878                        2
  879                    }
  880
  881                    fn c() {
  882                        3
  883                    }
  884                }
  885            "
  886            .unindent(),
  887            cx,
  888        );
  889        build_editor(buffer.clone(), window, cx)
  890    });
  891
  892    _ = editor.update(cx, |editor, window, cx| {
  893        editor.change_selections(None, window, cx, |s| {
  894            s.select_display_ranges([
  895                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  896            ]);
  897        });
  898        editor.fold(&Fold, window, cx);
  899        assert_eq!(
  900            editor.display_text(cx),
  901            "
  902                impl Foo {
  903                    // Hello!
  904
  905                    fn a() {
  906                        1
  907                    }
  908
  909                    fn b() {⋯
  910                    }
  911
  912                    fn c() {⋯
  913                    }
  914                }
  915            "
  916            .unindent(),
  917        );
  918
  919        editor.fold(&Fold, window, cx);
  920        assert_eq!(
  921            editor.display_text(cx),
  922            "
  923                impl Foo {⋯
  924                }
  925            "
  926            .unindent(),
  927        );
  928
  929        editor.unfold_lines(&UnfoldLines, window, cx);
  930        assert_eq!(
  931            editor.display_text(cx),
  932            "
  933                impl Foo {
  934                    // Hello!
  935
  936                    fn a() {
  937                        1
  938                    }
  939
  940                    fn b() {⋯
  941                    }
  942
  943                    fn c() {⋯
  944                    }
  945                }
  946            "
  947            .unindent(),
  948        );
  949
  950        editor.unfold_lines(&UnfoldLines, window, cx);
  951        assert_eq!(
  952            editor.display_text(cx),
  953            editor.buffer.read(cx).read(cx).text()
  954        );
  955    });
  956}
  957
  958#[gpui::test]
  959fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  960    init_test(cx, |_| {});
  961
  962    let editor = cx.add_window(|window, cx| {
  963        let buffer = MultiBuffer::build_simple(
  964            &"
  965                class Foo:
  966                    # Hello!
  967
  968                    def a():
  969                        print(1)
  970
  971                    def b():
  972                        print(2)
  973
  974                    def c():
  975                        print(3)
  976            "
  977            .unindent(),
  978            cx,
  979        );
  980        build_editor(buffer.clone(), window, cx)
  981    });
  982
  983    _ = editor.update(cx, |editor, window, cx| {
  984        editor.change_selections(None, window, cx, |s| {
  985            s.select_display_ranges([
  986                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  987            ]);
  988        });
  989        editor.fold(&Fold, window, cx);
  990        assert_eq!(
  991            editor.display_text(cx),
  992            "
  993                class Foo:
  994                    # Hello!
  995
  996                    def a():
  997                        print(1)
  998
  999                    def b():⋯
 1000
 1001                    def c():⋯
 1002            "
 1003            .unindent(),
 1004        );
 1005
 1006        editor.fold(&Fold, window, cx);
 1007        assert_eq!(
 1008            editor.display_text(cx),
 1009            "
 1010                class Foo:⋯
 1011            "
 1012            .unindent(),
 1013        );
 1014
 1015        editor.unfold_lines(&UnfoldLines, window, cx);
 1016        assert_eq!(
 1017            editor.display_text(cx),
 1018            "
 1019                class Foo:
 1020                    # Hello!
 1021
 1022                    def a():
 1023                        print(1)
 1024
 1025                    def b():⋯
 1026
 1027                    def c():⋯
 1028            "
 1029            .unindent(),
 1030        );
 1031
 1032        editor.unfold_lines(&UnfoldLines, window, cx);
 1033        assert_eq!(
 1034            editor.display_text(cx),
 1035            editor.buffer.read(cx).read(cx).text()
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                class Foo:
 1048                    # Hello!
 1049
 1050                    def a():
 1051                        print(1)
 1052
 1053                    def b():
 1054                        print(2)
 1055
 1056
 1057                    def c():
 1058                        print(3)
 1059
 1060
 1061            "
 1062            .unindent(),
 1063            cx,
 1064        );
 1065        build_editor(buffer.clone(), window, cx)
 1066    });
 1067
 1068    _ = editor.update(cx, |editor, window, cx| {
 1069        editor.change_selections(None, window, cx, |s| {
 1070            s.select_display_ranges([
 1071                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1072            ]);
 1073        });
 1074        editor.fold(&Fold, window, cx);
 1075        assert_eq!(
 1076            editor.display_text(cx),
 1077            "
 1078                class Foo:
 1079                    # Hello!
 1080
 1081                    def a():
 1082                        print(1)
 1083
 1084                    def b():⋯
 1085
 1086
 1087                    def c():⋯
 1088
 1089
 1090            "
 1091            .unindent(),
 1092        );
 1093
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:⋯
 1099
 1100
 1101            "
 1102            .unindent(),
 1103        );
 1104
 1105        editor.unfold_lines(&UnfoldLines, window, cx);
 1106        assert_eq!(
 1107            editor.display_text(cx),
 1108            "
 1109                class Foo:
 1110                    # Hello!
 1111
 1112                    def a():
 1113                        print(1)
 1114
 1115                    def b():⋯
 1116
 1117
 1118                    def c():⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            editor.buffer.read(cx).read(cx).text()
 1129        );
 1130    });
 1131}
 1132
 1133#[gpui::test]
 1134fn test_fold_at_level(cx: &mut TestAppContext) {
 1135    init_test(cx, |_| {});
 1136
 1137    let editor = cx.add_window(|window, cx| {
 1138        let buffer = MultiBuffer::build_simple(
 1139            &"
 1140                class Foo:
 1141                    # Hello!
 1142
 1143                    def a():
 1144                        print(1)
 1145
 1146                    def b():
 1147                        print(2)
 1148
 1149
 1150                class Bar:
 1151                    # World!
 1152
 1153                    def a():
 1154                        print(1)
 1155
 1156                    def b():
 1157                        print(2)
 1158
 1159
 1160            "
 1161            .unindent(),
 1162            cx,
 1163        );
 1164        build_editor(buffer.clone(), window, cx)
 1165    });
 1166
 1167    _ = editor.update(cx, |editor, window, cx| {
 1168        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1169        assert_eq!(
 1170            editor.display_text(cx),
 1171            "
 1172                class Foo:
 1173                    # Hello!
 1174
 1175                    def a():⋯
 1176
 1177                    def b():⋯
 1178
 1179
 1180                class Bar:
 1181                    # World!
 1182
 1183                    def a():⋯
 1184
 1185                    def b():⋯
 1186
 1187
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:⋯
 1197
 1198
 1199                class Bar:⋯
 1200
 1201
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.unfold_all(&UnfoldAll, window, cx);
 1207        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1208        assert_eq!(
 1209            editor.display_text(cx),
 1210            "
 1211                class Foo:
 1212                    # Hello!
 1213
 1214                    def a():
 1215                        print(1)
 1216
 1217                    def b():
 1218                        print(2)
 1219
 1220
 1221                class Bar:
 1222                    # World!
 1223
 1224                    def a():
 1225                        print(1)
 1226
 1227                    def b():
 1228                        print(2)
 1229
 1230
 1231            "
 1232            .unindent(),
 1233        );
 1234
 1235        assert_eq!(
 1236            editor.display_text(cx),
 1237            editor.buffer.read(cx).read(cx).text()
 1238        );
 1239    });
 1240}
 1241
 1242#[gpui::test]
 1243fn test_move_cursor(cx: &mut TestAppContext) {
 1244    init_test(cx, |_| {});
 1245
 1246    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1247    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1248
 1249    buffer.update(cx, |buffer, cx| {
 1250        buffer.edit(
 1251            vec![
 1252                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1253                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1254            ],
 1255            None,
 1256            cx,
 1257        );
 1258    });
 1259    _ = editor.update(cx, |editor, window, cx| {
 1260        assert_eq!(
 1261            editor.selections.display_ranges(cx),
 1262            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1263        );
 1264
 1265        editor.move_down(&MoveDown, window, cx);
 1266        assert_eq!(
 1267            editor.selections.display_ranges(cx),
 1268            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1269        );
 1270
 1271        editor.move_right(&MoveRight, window, cx);
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1275        );
 1276
 1277        editor.move_left(&MoveLeft, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_up(&MoveUp, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1287        );
 1288
 1289        editor.move_to_end(&MoveToEnd, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1293        );
 1294
 1295        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.change_selections(None, window, cx, |s| {
 1302            s.select_display_ranges([
 1303                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1304            ]);
 1305        });
 1306        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1307        assert_eq!(
 1308            editor.selections.display_ranges(cx),
 1309            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1310        );
 1311
 1312        editor.select_to_end(&SelectToEnd, window, cx);
 1313        assert_eq!(
 1314            editor.selections.display_ranges(cx),
 1315            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1316        );
 1317    });
 1318}
 1319
 1320#[gpui::test]
 1321fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1322    init_test(cx, |_| {});
 1323
 1324    let editor = cx.add_window(|window, cx| {
 1325        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1326        build_editor(buffer.clone(), window, cx)
 1327    });
 1328
 1329    assert_eq!('🟥'.len_utf8(), 4);
 1330    assert_eq!('α'.len_utf8(), 2);
 1331
 1332    _ = editor.update(cx, |editor, window, cx| {
 1333        editor.fold_creases(
 1334            vec![
 1335                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1336                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1337                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1338            ],
 1339            true,
 1340            window,
 1341            cx,
 1342        );
 1343        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1344
 1345        editor.move_right(&MoveRight, window, cx);
 1346        assert_eq!(
 1347            editor.selections.display_ranges(cx),
 1348            &[empty_range(0, "🟥".len())]
 1349        );
 1350        editor.move_right(&MoveRight, window, cx);
 1351        assert_eq!(
 1352            editor.selections.display_ranges(cx),
 1353            &[empty_range(0, "🟥🟧".len())]
 1354        );
 1355        editor.move_right(&MoveRight, window, cx);
 1356        assert_eq!(
 1357            editor.selections.display_ranges(cx),
 1358            &[empty_range(0, "🟥🟧⋯".len())]
 1359        );
 1360
 1361        editor.move_down(&MoveDown, window, cx);
 1362        assert_eq!(
 1363            editor.selections.display_ranges(cx),
 1364            &[empty_range(1, "ab⋯e".len())]
 1365        );
 1366        editor.move_left(&MoveLeft, window, cx);
 1367        assert_eq!(
 1368            editor.selections.display_ranges(cx),
 1369            &[empty_range(1, "ab⋯".len())]
 1370        );
 1371        editor.move_left(&MoveLeft, window, cx);
 1372        assert_eq!(
 1373            editor.selections.display_ranges(cx),
 1374            &[empty_range(1, "ab".len())]
 1375        );
 1376        editor.move_left(&MoveLeft, window, cx);
 1377        assert_eq!(
 1378            editor.selections.display_ranges(cx),
 1379            &[empty_range(1, "a".len())]
 1380        );
 1381
 1382        editor.move_down(&MoveDown, window, cx);
 1383        assert_eq!(
 1384            editor.selections.display_ranges(cx),
 1385            &[empty_range(2, "α".len())]
 1386        );
 1387        editor.move_right(&MoveRight, window, cx);
 1388        assert_eq!(
 1389            editor.selections.display_ranges(cx),
 1390            &[empty_range(2, "αβ".len())]
 1391        );
 1392        editor.move_right(&MoveRight, window, cx);
 1393        assert_eq!(
 1394            editor.selections.display_ranges(cx),
 1395            &[empty_range(2, "αβ⋯".len())]
 1396        );
 1397        editor.move_right(&MoveRight, window, cx);
 1398        assert_eq!(
 1399            editor.selections.display_ranges(cx),
 1400            &[empty_range(2, "αβ⋯ε".len())]
 1401        );
 1402
 1403        editor.move_up(&MoveUp, window, cx);
 1404        assert_eq!(
 1405            editor.selections.display_ranges(cx),
 1406            &[empty_range(1, "ab⋯e".len())]
 1407        );
 1408        editor.move_down(&MoveDown, window, cx);
 1409        assert_eq!(
 1410            editor.selections.display_ranges(cx),
 1411            &[empty_range(2, "αβ⋯ε".len())]
 1412        );
 1413        editor.move_up(&MoveUp, window, cx);
 1414        assert_eq!(
 1415            editor.selections.display_ranges(cx),
 1416            &[empty_range(1, "ab⋯e".len())]
 1417        );
 1418
 1419        editor.move_up(&MoveUp, window, cx);
 1420        assert_eq!(
 1421            editor.selections.display_ranges(cx),
 1422            &[empty_range(0, "🟥🟧".len())]
 1423        );
 1424        editor.move_left(&MoveLeft, window, cx);
 1425        assert_eq!(
 1426            editor.selections.display_ranges(cx),
 1427            &[empty_range(0, "🟥".len())]
 1428        );
 1429        editor.move_left(&MoveLeft, window, cx);
 1430        assert_eq!(
 1431            editor.selections.display_ranges(cx),
 1432            &[empty_range(0, "".len())]
 1433        );
 1434    });
 1435}
 1436
 1437#[gpui::test]
 1438fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1439    init_test(cx, |_| {});
 1440
 1441    let editor = cx.add_window(|window, cx| {
 1442        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1443        build_editor(buffer.clone(), window, cx)
 1444    });
 1445    _ = editor.update(cx, |editor, window, cx| {
 1446        editor.change_selections(None, window, cx, |s| {
 1447            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1448        });
 1449
 1450        // moving above start of document should move selection to start of document,
 1451        // but the next move down should still be at the original goal_x
 1452        editor.move_up(&MoveUp, window, cx);
 1453        assert_eq!(
 1454            editor.selections.display_ranges(cx),
 1455            &[empty_range(0, "".len())]
 1456        );
 1457
 1458        editor.move_down(&MoveDown, window, cx);
 1459        assert_eq!(
 1460            editor.selections.display_ranges(cx),
 1461            &[empty_range(1, "abcd".len())]
 1462        );
 1463
 1464        editor.move_down(&MoveDown, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(2, "αβγ".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(3, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1480        );
 1481
 1482        // moving past end of document should not change goal_x
 1483        editor.move_down(&MoveDown, window, cx);
 1484        assert_eq!(
 1485            editor.selections.display_ranges(cx),
 1486            &[empty_range(5, "".len())]
 1487        );
 1488
 1489        editor.move_down(&MoveDown, window, cx);
 1490        assert_eq!(
 1491            editor.selections.display_ranges(cx),
 1492            &[empty_range(5, "".len())]
 1493        );
 1494
 1495        editor.move_up(&MoveUp, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1499        );
 1500
 1501        editor.move_up(&MoveUp, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(3, "abcd".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(2, "αβγ".len())]
 1511        );
 1512    });
 1513}
 1514
 1515#[gpui::test]
 1516fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1517    init_test(cx, |_| {});
 1518    let move_to_beg = MoveToBeginningOfLine {
 1519        stop_at_soft_wraps: true,
 1520        stop_at_indent: true,
 1521    };
 1522
 1523    let delete_to_beg = DeleteToBeginningOfLine {
 1524        stop_at_indent: false,
 1525    };
 1526
 1527    let move_to_end = MoveToEndOfLine {
 1528        stop_at_soft_wraps: true,
 1529    };
 1530
 1531    let editor = cx.add_window(|window, cx| {
 1532        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1533        build_editor(buffer, window, cx)
 1534    });
 1535    _ = editor.update(cx, |editor, window, cx| {
 1536        editor.change_selections(None, window, cx, |s| {
 1537            s.select_display_ranges([
 1538                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1539                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1540            ]);
 1541        });
 1542    });
 1543
 1544    _ = editor.update(cx, |editor, window, cx| {
 1545        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1546        assert_eq!(
 1547            editor.selections.display_ranges(cx),
 1548            &[
 1549                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1550                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1551            ]
 1552        );
 1553    });
 1554
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1557        assert_eq!(
 1558            editor.selections.display_ranges(cx),
 1559            &[
 1560                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1561                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1562            ]
 1563        );
 1564    });
 1565
 1566    _ = editor.update(cx, |editor, window, cx| {
 1567        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1568        assert_eq!(
 1569            editor.selections.display_ranges(cx),
 1570            &[
 1571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1572                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1573            ]
 1574        );
 1575    });
 1576
 1577    _ = editor.update(cx, |editor, window, cx| {
 1578        editor.move_to_end_of_line(&move_to_end, window, cx);
 1579        assert_eq!(
 1580            editor.selections.display_ranges(cx),
 1581            &[
 1582                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1583                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1584            ]
 1585        );
 1586    });
 1587
 1588    // Moving to the end of line again is a no-op.
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    _ = editor.update(cx, |editor, window, cx| {
 1601        editor.move_left(&MoveLeft, window, cx);
 1602        editor.select_to_beginning_of_line(
 1603            &SelectToBeginningOfLine {
 1604                stop_at_soft_wraps: true,
 1605                stop_at_indent: true,
 1606            },
 1607            window,
 1608            cx,
 1609        );
 1610        assert_eq!(
 1611            editor.selections.display_ranges(cx),
 1612            &[
 1613                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1614                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1615            ]
 1616        );
 1617    });
 1618
 1619    _ = editor.update(cx, |editor, window, cx| {
 1620        editor.select_to_beginning_of_line(
 1621            &SelectToBeginningOfLine {
 1622                stop_at_soft_wraps: true,
 1623                stop_at_indent: true,
 1624            },
 1625            window,
 1626            cx,
 1627        );
 1628        assert_eq!(
 1629            editor.selections.display_ranges(cx),
 1630            &[
 1631                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1632                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1633            ]
 1634        );
 1635    });
 1636
 1637    _ = editor.update(cx, |editor, window, cx| {
 1638        editor.select_to_beginning_of_line(
 1639            &SelectToBeginningOfLine {
 1640                stop_at_soft_wraps: true,
 1641                stop_at_indent: true,
 1642            },
 1643            window,
 1644            cx,
 1645        );
 1646        assert_eq!(
 1647            editor.selections.display_ranges(cx),
 1648            &[
 1649                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1650                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1651            ]
 1652        );
 1653    });
 1654
 1655    _ = editor.update(cx, |editor, window, cx| {
 1656        editor.select_to_end_of_line(
 1657            &SelectToEndOfLine {
 1658                stop_at_soft_wraps: true,
 1659            },
 1660            window,
 1661            cx,
 1662        );
 1663        assert_eq!(
 1664            editor.selections.display_ranges(cx),
 1665            &[
 1666                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1667                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1668            ]
 1669        );
 1670    });
 1671
 1672    _ = editor.update(cx, |editor, window, cx| {
 1673        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1674        assert_eq!(editor.display_text(cx), "ab\n  de");
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1686        assert_eq!(editor.display_text(cx), "\n");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1691                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1692            ]
 1693        );
 1694    });
 1695}
 1696
 1697#[gpui::test]
 1698fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1699    init_test(cx, |_| {});
 1700    let move_to_beg = MoveToBeginningOfLine {
 1701        stop_at_soft_wraps: false,
 1702        stop_at_indent: false,
 1703    };
 1704
 1705    let move_to_end = MoveToEndOfLine {
 1706        stop_at_soft_wraps: false,
 1707    };
 1708
 1709    let editor = cx.add_window(|window, cx| {
 1710        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1711        build_editor(buffer, window, cx)
 1712    });
 1713
 1714    _ = editor.update(cx, |editor, window, cx| {
 1715        editor.set_wrap_width(Some(140.0.into()), cx);
 1716
 1717        // We expect the following lines after wrapping
 1718        // ```
 1719        // thequickbrownfox
 1720        // jumpedoverthelazydo
 1721        // gs
 1722        // ```
 1723        // The final `gs` was soft-wrapped onto a new line.
 1724        assert_eq!(
 1725            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1726            editor.display_text(cx),
 1727        );
 1728
 1729        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1730        // Start the cursor at the `k` on the first line
 1731        editor.change_selections(None, window, cx, |s| {
 1732            s.select_display_ranges([
 1733                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1734            ]);
 1735        });
 1736
 1737        // Moving to the beginning of the line should put us at the beginning of the line.
 1738        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1739        assert_eq!(
 1740            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1741            editor.selections.display_ranges(cx)
 1742        );
 1743
 1744        // Moving to the end of the line should put us at the end of the line.
 1745        editor.move_to_end_of_line(&move_to_end, window, cx);
 1746        assert_eq!(
 1747            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1748            editor.selections.display_ranges(cx)
 1749        );
 1750
 1751        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1752        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1753        editor.change_selections(None, window, cx, |s| {
 1754            s.select_display_ranges([
 1755                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1756            ]);
 1757        });
 1758
 1759        // Moving to the beginning of the line should put us at the start of the second line of
 1760        // display text, i.e., the `j`.
 1761        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1762        assert_eq!(
 1763            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1764            editor.selections.display_ranges(cx)
 1765        );
 1766
 1767        // Moving to the beginning of the line again should be a no-op.
 1768        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1769        assert_eq!(
 1770            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1771            editor.selections.display_ranges(cx)
 1772        );
 1773
 1774        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1775        // next display line.
 1776        editor.move_to_end_of_line(&move_to_end, window, cx);
 1777        assert_eq!(
 1778            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1779            editor.selections.display_ranges(cx)
 1780        );
 1781
 1782        // Moving to the end of the line again should be a no-op.
 1783        editor.move_to_end_of_line(&move_to_end, window, cx);
 1784        assert_eq!(
 1785            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1786            editor.selections.display_ranges(cx)
 1787        );
 1788    });
 1789}
 1790
 1791#[gpui::test]
 1792fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1793    init_test(cx, |_| {});
 1794
 1795    let move_to_beg = MoveToBeginningOfLine {
 1796        stop_at_soft_wraps: true,
 1797        stop_at_indent: true,
 1798    };
 1799
 1800    let select_to_beg = SelectToBeginningOfLine {
 1801        stop_at_soft_wraps: true,
 1802        stop_at_indent: true,
 1803    };
 1804
 1805    let delete_to_beg = DeleteToBeginningOfLine {
 1806        stop_at_indent: true,
 1807    };
 1808
 1809    let move_to_end = MoveToEndOfLine {
 1810        stop_at_soft_wraps: false,
 1811    };
 1812
 1813    let editor = cx.add_window(|window, cx| {
 1814        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1815        build_editor(buffer, window, cx)
 1816    });
 1817
 1818    _ = editor.update(cx, |editor, window, cx| {
 1819        editor.change_selections(None, window, cx, |s| {
 1820            s.select_display_ranges([
 1821                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1822                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1823            ]);
 1824        });
 1825
 1826        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1827        // and the second cursor at the first non-whitespace character in the line.
 1828        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1829        assert_eq!(
 1830            editor.selections.display_ranges(cx),
 1831            &[
 1832                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1833                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1834            ]
 1835        );
 1836
 1837        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1838        // and should move the second cursor to the beginning of the line.
 1839        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1840        assert_eq!(
 1841            editor.selections.display_ranges(cx),
 1842            &[
 1843                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1844                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1845            ]
 1846        );
 1847
 1848        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1849        // and should move the second cursor back to the first non-whitespace character in the line.
 1850        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1851        assert_eq!(
 1852            editor.selections.display_ranges(cx),
 1853            &[
 1854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1855                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1856            ]
 1857        );
 1858
 1859        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1860        // and to the first non-whitespace character in the line for the second cursor.
 1861        editor.move_to_end_of_line(&move_to_end, window, cx);
 1862        editor.move_left(&MoveLeft, window, cx);
 1863        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1869            ]
 1870        );
 1871
 1872        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1873        // and should select to the beginning of the line for the second cursor.
 1874        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1875        assert_eq!(
 1876            editor.selections.display_ranges(cx),
 1877            &[
 1878                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1879                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1880            ]
 1881        );
 1882
 1883        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1884        // and should delete to the first non-whitespace character in the line for the second cursor.
 1885        editor.move_to_end_of_line(&move_to_end, window, cx);
 1886        editor.move_left(&MoveLeft, window, cx);
 1887        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1888        assert_eq!(editor.text(cx), "c\n  f");
 1889    });
 1890}
 1891
 1892#[gpui::test]
 1893fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1894    init_test(cx, |_| {});
 1895
 1896    let editor = cx.add_window(|window, cx| {
 1897        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1898        build_editor(buffer, window, cx)
 1899    });
 1900    _ = editor.update(cx, |editor, window, cx| {
 1901        editor.change_selections(None, window, cx, |s| {
 1902            s.select_display_ranges([
 1903                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1904                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1905            ])
 1906        });
 1907
 1908        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1909        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1910
 1911        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1912        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1913
 1914        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1915        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1916
 1917        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1918        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1919
 1920        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1921        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1922
 1923        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1924        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1925
 1926        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1927        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1928
 1929        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1930        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1931
 1932        editor.move_right(&MoveRight, window, cx);
 1933        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1934        assert_selection_ranges(
 1935            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1936            editor,
 1937            cx,
 1938        );
 1939
 1940        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1941        assert_selection_ranges(
 1942            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1943            editor,
 1944            cx,
 1945        );
 1946
 1947        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1948        assert_selection_ranges(
 1949            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1950            editor,
 1951            cx,
 1952        );
 1953    });
 1954}
 1955
 1956#[gpui::test]
 1957fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1958    init_test(cx, |_| {});
 1959
 1960    let editor = cx.add_window(|window, cx| {
 1961        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1962        build_editor(buffer, window, cx)
 1963    });
 1964
 1965    _ = editor.update(cx, |editor, window, cx| {
 1966        editor.set_wrap_width(Some(140.0.into()), cx);
 1967        assert_eq!(
 1968            editor.display_text(cx),
 1969            "use one::{\n    two::three::\n    four::five\n};"
 1970        );
 1971
 1972        editor.change_selections(None, window, cx, |s| {
 1973            s.select_display_ranges([
 1974                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1975            ]);
 1976        });
 1977
 1978        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1979        assert_eq!(
 1980            editor.selections.display_ranges(cx),
 1981            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1982        );
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_eq!(
 1986            editor.selections.display_ranges(cx),
 1987            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1988        );
 1989
 1990        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1991        assert_eq!(
 1992            editor.selections.display_ranges(cx),
 1993            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1994        );
 1995
 1996        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1997        assert_eq!(
 1998            editor.selections.display_ranges(cx),
 1999            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2000        );
 2001
 2002        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2003        assert_eq!(
 2004            editor.selections.display_ranges(cx),
 2005            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2006        );
 2007
 2008        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2009        assert_eq!(
 2010            editor.selections.display_ranges(cx),
 2011            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2012        );
 2013    });
 2014}
 2015
 2016#[gpui::test]
 2017async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2018    init_test(cx, |_| {});
 2019    let mut cx = EditorTestContext::new(cx).await;
 2020
 2021    let line_height = cx.editor(|editor, window, _| {
 2022        editor
 2023            .style()
 2024            .unwrap()
 2025            .text
 2026            .line_height_in_pixels(window.rem_size())
 2027    });
 2028    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2029
 2030    cx.set_state(
 2031        &r#"ˇone
 2032        two
 2033
 2034        three
 2035        fourˇ
 2036        five
 2037
 2038        six"#
 2039            .unindent(),
 2040    );
 2041
 2042    cx.update_editor(|editor, window, cx| {
 2043        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2044    });
 2045    cx.assert_editor_state(
 2046        &r#"one
 2047        two
 2048        ˇ
 2049        three
 2050        four
 2051        five
 2052        ˇ
 2053        six"#
 2054            .unindent(),
 2055    );
 2056
 2057    cx.update_editor(|editor, window, cx| {
 2058        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2059    });
 2060    cx.assert_editor_state(
 2061        &r#"one
 2062        two
 2063
 2064        three
 2065        four
 2066        five
 2067        ˇ
 2068        sixˇ"#
 2069            .unindent(),
 2070    );
 2071
 2072    cx.update_editor(|editor, window, cx| {
 2073        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2074    });
 2075    cx.assert_editor_state(
 2076        &r#"one
 2077        two
 2078
 2079        three
 2080        four
 2081        five
 2082
 2083        sixˇ"#
 2084            .unindent(),
 2085    );
 2086
 2087    cx.update_editor(|editor, window, cx| {
 2088        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2089    });
 2090    cx.assert_editor_state(
 2091        &r#"one
 2092        two
 2093
 2094        three
 2095        four
 2096        five
 2097        ˇ
 2098        six"#
 2099            .unindent(),
 2100    );
 2101
 2102    cx.update_editor(|editor, window, cx| {
 2103        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2104    });
 2105    cx.assert_editor_state(
 2106        &r#"one
 2107        two
 2108        ˇ
 2109        three
 2110        four
 2111        five
 2112
 2113        six"#
 2114            .unindent(),
 2115    );
 2116
 2117    cx.update_editor(|editor, window, cx| {
 2118        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2119    });
 2120    cx.assert_editor_state(
 2121        &r#"ˇone
 2122        two
 2123
 2124        three
 2125        four
 2126        five
 2127
 2128        six"#
 2129            .unindent(),
 2130    );
 2131}
 2132
 2133#[gpui::test]
 2134async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2135    init_test(cx, |_| {});
 2136    let mut cx = EditorTestContext::new(cx).await;
 2137    let line_height = cx.editor(|editor, window, _| {
 2138        editor
 2139            .style()
 2140            .unwrap()
 2141            .text
 2142            .line_height_in_pixels(window.rem_size())
 2143    });
 2144    let window = cx.window;
 2145    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2146
 2147    cx.set_state(
 2148        r#"ˇone
 2149        two
 2150        three
 2151        four
 2152        five
 2153        six
 2154        seven
 2155        eight
 2156        nine
 2157        ten
 2158        "#,
 2159    );
 2160
 2161    cx.update_editor(|editor, window, cx| {
 2162        assert_eq!(
 2163            editor.snapshot(window, cx).scroll_position(),
 2164            gpui::Point::new(0., 0.)
 2165        );
 2166        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2167        assert_eq!(
 2168            editor.snapshot(window, cx).scroll_position(),
 2169            gpui::Point::new(0., 3.)
 2170        );
 2171        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2172        assert_eq!(
 2173            editor.snapshot(window, cx).scroll_position(),
 2174            gpui::Point::new(0., 6.)
 2175        );
 2176        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2177        assert_eq!(
 2178            editor.snapshot(window, cx).scroll_position(),
 2179            gpui::Point::new(0., 3.)
 2180        );
 2181
 2182        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 1.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192    });
 2193}
 2194
 2195#[gpui::test]
 2196async fn test_autoscroll(cx: &mut TestAppContext) {
 2197    init_test(cx, |_| {});
 2198    let mut cx = EditorTestContext::new(cx).await;
 2199
 2200    let line_height = cx.update_editor(|editor, window, cx| {
 2201        editor.set_vertical_scroll_margin(2, cx);
 2202        editor
 2203            .style()
 2204            .unwrap()
 2205            .text
 2206            .line_height_in_pixels(window.rem_size())
 2207    });
 2208    let window = cx.window;
 2209    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2210
 2211    cx.set_state(
 2212        r#"ˇone
 2213            two
 2214            three
 2215            four
 2216            five
 2217            six
 2218            seven
 2219            eight
 2220            nine
 2221            ten
 2222        "#,
 2223    );
 2224    cx.update_editor(|editor, window, cx| {
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 0.0)
 2228        );
 2229    });
 2230
 2231    // Add a cursor below the visible area. Since both cursors cannot fit
 2232    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2233    // allows the vertical scroll margin below that cursor.
 2234    cx.update_editor(|editor, window, cx| {
 2235        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2236            selections.select_ranges([
 2237                Point::new(0, 0)..Point::new(0, 0),
 2238                Point::new(6, 0)..Point::new(6, 0),
 2239            ]);
 2240        })
 2241    });
 2242    cx.update_editor(|editor, window, cx| {
 2243        assert_eq!(
 2244            editor.snapshot(window, cx).scroll_position(),
 2245            gpui::Point::new(0., 3.0)
 2246        );
 2247    });
 2248
 2249    // Move down. The editor cursor scrolls down to track the newest cursor.
 2250    cx.update_editor(|editor, window, cx| {
 2251        editor.move_down(&Default::default(), window, cx);
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 4.0)
 2257        );
 2258    });
 2259
 2260    // Add a cursor above the visible area. Since both cursors fit on screen,
 2261    // the editor scrolls to show both.
 2262    cx.update_editor(|editor, window, cx| {
 2263        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2264            selections.select_ranges([
 2265                Point::new(1, 0)..Point::new(1, 0),
 2266                Point::new(6, 0)..Point::new(6, 0),
 2267            ]);
 2268        })
 2269    });
 2270    cx.update_editor(|editor, window, cx| {
 2271        assert_eq!(
 2272            editor.snapshot(window, cx).scroll_position(),
 2273            gpui::Point::new(0., 1.0)
 2274        );
 2275    });
 2276}
 2277
 2278#[gpui::test]
 2279async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2280    init_test(cx, |_| {});
 2281    let mut cx = EditorTestContext::new(cx).await;
 2282
 2283    let line_height = cx.editor(|editor, window, _cx| {
 2284        editor
 2285            .style()
 2286            .unwrap()
 2287            .text
 2288            .line_height_in_pixels(window.rem_size())
 2289    });
 2290    let window = cx.window;
 2291    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2292    cx.set_state(
 2293        &r#"
 2294        ˇone
 2295        two
 2296        threeˇ
 2297        four
 2298        five
 2299        six
 2300        seven
 2301        eight
 2302        nine
 2303        ten
 2304        "#
 2305        .unindent(),
 2306    );
 2307
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_page_down(&MovePageDown::default(), window, cx)
 2310    });
 2311    cx.assert_editor_state(
 2312        &r#"
 2313        one
 2314        two
 2315        three
 2316        ˇfour
 2317        five
 2318        sixˇ
 2319        seven
 2320        eight
 2321        nine
 2322        ten
 2323        "#
 2324        .unindent(),
 2325    );
 2326
 2327    cx.update_editor(|editor, window, cx| {
 2328        editor.move_page_down(&MovePageDown::default(), window, cx)
 2329    });
 2330    cx.assert_editor_state(
 2331        &r#"
 2332        one
 2333        two
 2334        three
 2335        four
 2336        five
 2337        six
 2338        ˇseven
 2339        eight
 2340        nineˇ
 2341        ten
 2342        "#
 2343        .unindent(),
 2344    );
 2345
 2346    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2347    cx.assert_editor_state(
 2348        &r#"
 2349        one
 2350        two
 2351        three
 2352        ˇfour
 2353        five
 2354        sixˇ
 2355        seven
 2356        eight
 2357        nine
 2358        ten
 2359        "#
 2360        .unindent(),
 2361    );
 2362
 2363    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2364    cx.assert_editor_state(
 2365        &r#"
 2366        ˇone
 2367        two
 2368        threeˇ
 2369        four
 2370        five
 2371        six
 2372        seven
 2373        eight
 2374        nine
 2375        ten
 2376        "#
 2377        .unindent(),
 2378    );
 2379
 2380    // Test select collapsing
 2381    cx.update_editor(|editor, window, cx| {
 2382        editor.move_page_down(&MovePageDown::default(), window, cx);
 2383        editor.move_page_down(&MovePageDown::default(), window, cx);
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385    });
 2386    cx.assert_editor_state(
 2387        &r#"
 2388        one
 2389        two
 2390        three
 2391        four
 2392        five
 2393        six
 2394        seven
 2395        eight
 2396        nine
 2397        ˇten
 2398        ˇ"#
 2399        .unindent(),
 2400    );
 2401}
 2402
 2403#[gpui::test]
 2404async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2405    init_test(cx, |_| {});
 2406    let mut cx = EditorTestContext::new(cx).await;
 2407    cx.set_state("one «two threeˇ» four");
 2408    cx.update_editor(|editor, window, cx| {
 2409        editor.delete_to_beginning_of_line(
 2410            &DeleteToBeginningOfLine {
 2411                stop_at_indent: false,
 2412            },
 2413            window,
 2414            cx,
 2415        );
 2416        assert_eq!(editor.text(cx), " four");
 2417    });
 2418}
 2419
 2420#[gpui::test]
 2421fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2422    init_test(cx, |_| {});
 2423
 2424    let editor = cx.add_window(|window, cx| {
 2425        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2426        build_editor(buffer.clone(), window, cx)
 2427    });
 2428
 2429    _ = editor.update(cx, |editor, window, cx| {
 2430        editor.change_selections(None, window, cx, |s| {
 2431            s.select_display_ranges([
 2432                // an empty selection - the preceding word fragment is deleted
 2433                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2434                // characters selected - they are deleted
 2435                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2436            ])
 2437        });
 2438        editor.delete_to_previous_word_start(
 2439            &DeleteToPreviousWordStart {
 2440                ignore_newlines: false,
 2441            },
 2442            window,
 2443            cx,
 2444        );
 2445        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2446    });
 2447
 2448    _ = editor.update(cx, |editor, window, cx| {
 2449        editor.change_selections(None, window, cx, |s| {
 2450            s.select_display_ranges([
 2451                // an empty selection - the following word fragment is deleted
 2452                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2453                // characters selected - they are deleted
 2454                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2455            ])
 2456        });
 2457        editor.delete_to_next_word_end(
 2458            &DeleteToNextWordEnd {
 2459                ignore_newlines: false,
 2460            },
 2461            window,
 2462            cx,
 2463        );
 2464        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2465    });
 2466}
 2467
 2468#[gpui::test]
 2469fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2470    init_test(cx, |_| {});
 2471
 2472    let editor = cx.add_window(|window, cx| {
 2473        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2474        build_editor(buffer.clone(), window, cx)
 2475    });
 2476    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2477        ignore_newlines: false,
 2478    };
 2479    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2480        ignore_newlines: true,
 2481    };
 2482
 2483    _ = editor.update(cx, |editor, window, cx| {
 2484        editor.change_selections(None, window, cx, |s| {
 2485            s.select_display_ranges([
 2486                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2487            ])
 2488        });
 2489        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2490        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2491        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2492        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2493        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2494        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2495        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2496        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2497        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2498        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2499        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2500        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2501    });
 2502}
 2503
 2504#[gpui::test]
 2505fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2506    init_test(cx, |_| {});
 2507
 2508    let editor = cx.add_window(|window, cx| {
 2509        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2510        build_editor(buffer.clone(), window, cx)
 2511    });
 2512    let del_to_next_word_end = DeleteToNextWordEnd {
 2513        ignore_newlines: false,
 2514    };
 2515    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2516        ignore_newlines: true,
 2517    };
 2518
 2519    _ = editor.update(cx, |editor, window, cx| {
 2520        editor.change_selections(None, window, cx, |s| {
 2521            s.select_display_ranges([
 2522                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2523            ])
 2524        });
 2525        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2526        assert_eq!(
 2527            editor.buffer.read(cx).read(cx).text(),
 2528            "one\n   two\nthree\n   four"
 2529        );
 2530        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2531        assert_eq!(
 2532            editor.buffer.read(cx).read(cx).text(),
 2533            "\n   two\nthree\n   four"
 2534        );
 2535        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2536        assert_eq!(
 2537            editor.buffer.read(cx).read(cx).text(),
 2538            "two\nthree\n   four"
 2539        );
 2540        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2541        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2542        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2543        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2544        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2545        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2546    });
 2547}
 2548
 2549#[gpui::test]
 2550fn test_newline(cx: &mut TestAppContext) {
 2551    init_test(cx, |_| {});
 2552
 2553    let editor = cx.add_window(|window, cx| {
 2554        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2555        build_editor(buffer.clone(), window, cx)
 2556    });
 2557
 2558    _ = editor.update(cx, |editor, window, cx| {
 2559        editor.change_selections(None, window, cx, |s| {
 2560            s.select_display_ranges([
 2561                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2563                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2564            ])
 2565        });
 2566
 2567        editor.newline(&Newline, window, cx);
 2568        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2569    });
 2570}
 2571
 2572#[gpui::test]
 2573fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2574    init_test(cx, |_| {});
 2575
 2576    let editor = cx.add_window(|window, cx| {
 2577        let buffer = MultiBuffer::build_simple(
 2578            "
 2579                a
 2580                b(
 2581                    X
 2582                )
 2583                c(
 2584                    X
 2585                )
 2586            "
 2587            .unindent()
 2588            .as_str(),
 2589            cx,
 2590        );
 2591        let mut editor = build_editor(buffer.clone(), window, cx);
 2592        editor.change_selections(None, window, cx, |s| {
 2593            s.select_ranges([
 2594                Point::new(2, 4)..Point::new(2, 5),
 2595                Point::new(5, 4)..Point::new(5, 5),
 2596            ])
 2597        });
 2598        editor
 2599    });
 2600
 2601    _ = editor.update(cx, |editor, window, cx| {
 2602        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2603        editor.buffer.update(cx, |buffer, cx| {
 2604            buffer.edit(
 2605                [
 2606                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2607                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2608                ],
 2609                None,
 2610                cx,
 2611            );
 2612            assert_eq!(
 2613                buffer.read(cx).text(),
 2614                "
 2615                    a
 2616                    b()
 2617                    c()
 2618                "
 2619                .unindent()
 2620            );
 2621        });
 2622        assert_eq!(
 2623            editor.selections.ranges(cx),
 2624            &[
 2625                Point::new(1, 2)..Point::new(1, 2),
 2626                Point::new(2, 2)..Point::new(2, 2),
 2627            ],
 2628        );
 2629
 2630        editor.newline(&Newline, window, cx);
 2631        assert_eq!(
 2632            editor.text(cx),
 2633            "
 2634                a
 2635                b(
 2636                )
 2637                c(
 2638                )
 2639            "
 2640            .unindent()
 2641        );
 2642
 2643        // The selections are moved after the inserted newlines
 2644        assert_eq!(
 2645            editor.selections.ranges(cx),
 2646            &[
 2647                Point::new(2, 0)..Point::new(2, 0),
 2648                Point::new(4, 0)..Point::new(4, 0),
 2649            ],
 2650        );
 2651    });
 2652}
 2653
 2654#[gpui::test]
 2655async fn test_newline_above(cx: &mut TestAppContext) {
 2656    init_test(cx, |settings| {
 2657        settings.defaults.tab_size = NonZeroU32::new(4)
 2658    });
 2659
 2660    let language = Arc::new(
 2661        Language::new(
 2662            LanguageConfig::default(),
 2663            Some(tree_sitter_rust::LANGUAGE.into()),
 2664        )
 2665        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2666        .unwrap(),
 2667    );
 2668
 2669    let mut cx = EditorTestContext::new(cx).await;
 2670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2671    cx.set_state(indoc! {"
 2672        const a: ˇA = (
 2673 2674                «const_functionˇ»(ˇ),
 2675                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2676 2677        ˇ);ˇ
 2678    "});
 2679
 2680    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2681    cx.assert_editor_state(indoc! {"
 2682        ˇ
 2683        const a: A = (
 2684            ˇ
 2685            (
 2686                ˇ
 2687                ˇ
 2688                const_function(),
 2689                ˇ
 2690                ˇ
 2691                ˇ
 2692                ˇ
 2693                something_else,
 2694                ˇ
 2695            )
 2696            ˇ
 2697            ˇ
 2698        );
 2699    "});
 2700}
 2701
 2702#[gpui::test]
 2703async fn test_newline_below(cx: &mut TestAppContext) {
 2704    init_test(cx, |settings| {
 2705        settings.defaults.tab_size = NonZeroU32::new(4)
 2706    });
 2707
 2708    let language = Arc::new(
 2709        Language::new(
 2710            LanguageConfig::default(),
 2711            Some(tree_sitter_rust::LANGUAGE.into()),
 2712        )
 2713        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2714        .unwrap(),
 2715    );
 2716
 2717    let mut cx = EditorTestContext::new(cx).await;
 2718    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2719    cx.set_state(indoc! {"
 2720        const a: ˇA = (
 2721 2722                «const_functionˇ»(ˇ),
 2723                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2724 2725        ˇ);ˇ
 2726    "});
 2727
 2728    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2729    cx.assert_editor_state(indoc! {"
 2730        const a: A = (
 2731            ˇ
 2732            (
 2733                ˇ
 2734                const_function(),
 2735                ˇ
 2736                ˇ
 2737                something_else,
 2738                ˇ
 2739                ˇ
 2740                ˇ
 2741                ˇ
 2742            )
 2743            ˇ
 2744        );
 2745        ˇ
 2746        ˇ
 2747    "});
 2748}
 2749
 2750#[gpui::test]
 2751async fn test_newline_comments(cx: &mut TestAppContext) {
 2752    init_test(cx, |settings| {
 2753        settings.defaults.tab_size = NonZeroU32::new(4)
 2754    });
 2755
 2756    let language = Arc::new(Language::new(
 2757        LanguageConfig {
 2758            line_comments: vec!["//".into()],
 2759            ..LanguageConfig::default()
 2760        },
 2761        None,
 2762    ));
 2763    {
 2764        let mut cx = EditorTestContext::new(cx).await;
 2765        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2766        cx.set_state(indoc! {"
 2767        // Fooˇ
 2768    "});
 2769
 2770        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2771        cx.assert_editor_state(indoc! {"
 2772        // Foo
 2773        //ˇ
 2774    "});
 2775        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2776        cx.set_state(indoc! {"
 2777        ˇ// Foo
 2778    "});
 2779        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2780        cx.assert_editor_state(indoc! {"
 2781
 2782        ˇ// Foo
 2783    "});
 2784    }
 2785    // Ensure that comment continuations can be disabled.
 2786    update_test_language_settings(cx, |settings| {
 2787        settings.defaults.extend_comment_on_newline = Some(false);
 2788    });
 2789    let mut cx = EditorTestContext::new(cx).await;
 2790    cx.set_state(indoc! {"
 2791        // Fooˇ
 2792    "});
 2793    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2794    cx.assert_editor_state(indoc! {"
 2795        // Foo
 2796        ˇ
 2797    "});
 2798}
 2799
 2800#[gpui::test]
 2801async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2802    init_test(cx, |settings| {
 2803        settings.defaults.tab_size = NonZeroU32::new(4)
 2804    });
 2805
 2806    let language = Arc::new(Language::new(
 2807        LanguageConfig {
 2808            documentation: Some(language::DocumentationConfig {
 2809                start: "/**".into(),
 2810                end: "*/".into(),
 2811                prefix: "* ".into(),
 2812                tab_size: NonZeroU32::new(1).unwrap(),
 2813            }),
 2814            ..LanguageConfig::default()
 2815        },
 2816        None,
 2817    ));
 2818    {
 2819        let mut cx = EditorTestContext::new(cx).await;
 2820        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2821        cx.set_state(indoc! {"
 2822        /**ˇ
 2823    "});
 2824
 2825        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2826        cx.assert_editor_state(indoc! {"
 2827        /**
 2828         * ˇ
 2829    "});
 2830        // Ensure that if cursor is before the comment start,
 2831        // we do not actually insert a comment prefix.
 2832        cx.set_state(indoc! {"
 2833        ˇ/**
 2834    "});
 2835        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2836        cx.assert_editor_state(indoc! {"
 2837
 2838        ˇ/**
 2839    "});
 2840        // Ensure that if cursor is between it doesn't add comment prefix.
 2841        cx.set_state(indoc! {"
 2842        /*ˇ*
 2843    "});
 2844        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2845        cx.assert_editor_state(indoc! {"
 2846        /*
 2847        ˇ*
 2848    "});
 2849        // Ensure that if suffix exists on same line after cursor it adds new line.
 2850        cx.set_state(indoc! {"
 2851        /**ˇ*/
 2852    "});
 2853        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2854        cx.assert_editor_state(indoc! {"
 2855        /**
 2856         * ˇ
 2857         */
 2858    "});
 2859        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2860        cx.set_state(indoc! {"
 2861        /**ˇ */
 2862    "});
 2863        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2864        cx.assert_editor_state(indoc! {"
 2865        /**
 2866         * ˇ
 2867         */
 2868    "});
 2869        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2870        cx.set_state(indoc! {"
 2871        /** ˇ*/
 2872    "});
 2873        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2874        cx.assert_editor_state(
 2875            indoc! {"
 2876        /**s
 2877         * ˇ
 2878         */
 2879    "}
 2880            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2881            .as_str(),
 2882        );
 2883        // Ensure that delimiter space is preserved when newline on already
 2884        // spaced delimiter.
 2885        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2886        cx.assert_editor_state(
 2887            indoc! {"
 2888        /**s
 2889         *s
 2890         * ˇ
 2891         */
 2892    "}
 2893            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2894            .as_str(),
 2895        );
 2896        // Ensure that delimiter space is preserved when space is not
 2897        // on existing delimiter.
 2898        cx.set_state(indoc! {"
 2899        /**
 2900 2901         */
 2902    "});
 2903        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2904        cx.assert_editor_state(indoc! {"
 2905        /**
 2906         *
 2907         * ˇ
 2908         */
 2909    "});
 2910        // Ensure that if suffix exists on same line after cursor it
 2911        // doesn't add extra new line if prefix is not on same line.
 2912        cx.set_state(indoc! {"
 2913        /**
 2914        ˇ*/
 2915    "});
 2916        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2917        cx.assert_editor_state(indoc! {"
 2918        /**
 2919
 2920        ˇ*/
 2921    "});
 2922        // Ensure that it detects suffix after existing prefix.
 2923        cx.set_state(indoc! {"
 2924        /**ˇ/
 2925    "});
 2926        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2927        cx.assert_editor_state(indoc! {"
 2928        /**
 2929        ˇ/
 2930    "});
 2931        // Ensure that if suffix exists on same line before
 2932        // cursor it does not add comment prefix.
 2933        cx.set_state(indoc! {"
 2934        /** */ˇ
 2935    "});
 2936        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2937        cx.assert_editor_state(indoc! {"
 2938        /** */
 2939        ˇ
 2940    "});
 2941        // Ensure that if suffix exists on same line before
 2942        // cursor it does not add comment prefix.
 2943        cx.set_state(indoc! {"
 2944        /**
 2945         *
 2946         */ˇ
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(indoc! {"
 2950        /**
 2951         *
 2952         */
 2953         ˇ
 2954    "});
 2955    }
 2956    // Ensure that comment continuations can be disabled.
 2957    update_test_language_settings(cx, |settings| {
 2958        settings.defaults.extend_comment_on_newline = Some(false);
 2959    });
 2960    let mut cx = EditorTestContext::new(cx).await;
 2961    cx.set_state(indoc! {"
 2962        /**ˇ
 2963    "});
 2964    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2965    cx.assert_editor_state(indoc! {"
 2966        /**
 2967        ˇ
 2968    "});
 2969}
 2970
 2971#[gpui::test]
 2972fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2973    init_test(cx, |_| {});
 2974
 2975    let editor = cx.add_window(|window, cx| {
 2976        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2977        let mut editor = build_editor(buffer.clone(), window, cx);
 2978        editor.change_selections(None, window, cx, |s| {
 2979            s.select_ranges([3..4, 11..12, 19..20])
 2980        });
 2981        editor
 2982    });
 2983
 2984    _ = editor.update(cx, |editor, window, cx| {
 2985        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2986        editor.buffer.update(cx, |buffer, cx| {
 2987            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2988            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2989        });
 2990        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2991
 2992        editor.insert("Z", window, cx);
 2993        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2994
 2995        // The selections are moved after the inserted characters
 2996        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2997    });
 2998}
 2999
 3000#[gpui::test]
 3001async fn test_tab(cx: &mut TestAppContext) {
 3002    init_test(cx, |settings| {
 3003        settings.defaults.tab_size = NonZeroU32::new(3)
 3004    });
 3005
 3006    let mut cx = EditorTestContext::new(cx).await;
 3007    cx.set_state(indoc! {"
 3008        ˇabˇc
 3009        ˇ🏀ˇ🏀ˇefg
 3010 3011    "});
 3012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3013    cx.assert_editor_state(indoc! {"
 3014           ˇab ˇc
 3015           ˇ🏀  ˇ🏀  ˇefg
 3016        d  ˇ
 3017    "});
 3018
 3019    cx.set_state(indoc! {"
 3020        a
 3021        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3022    "});
 3023    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3024    cx.assert_editor_state(indoc! {"
 3025        a
 3026           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3027    "});
 3028}
 3029
 3030#[gpui::test]
 3031async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3032    init_test(cx, |_| {});
 3033
 3034    let mut cx = EditorTestContext::new(cx).await;
 3035    let language = Arc::new(
 3036        Language::new(
 3037            LanguageConfig::default(),
 3038            Some(tree_sitter_rust::LANGUAGE.into()),
 3039        )
 3040        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3041        .unwrap(),
 3042    );
 3043    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3044
 3045    // test when all cursors are not at suggested indent
 3046    // then simply move to their suggested indent location
 3047    cx.set_state(indoc! {"
 3048        const a: B = (
 3049            c(
 3050        ˇ
 3051        ˇ    )
 3052        );
 3053    "});
 3054    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3055    cx.assert_editor_state(indoc! {"
 3056        const a: B = (
 3057            c(
 3058                ˇ
 3059            ˇ)
 3060        );
 3061    "});
 3062
 3063    // test cursor already at suggested indent not moving when
 3064    // other cursors are yet to reach their suggested indents
 3065    cx.set_state(indoc! {"
 3066        ˇ
 3067        const a: B = (
 3068            c(
 3069                d(
 3070        ˇ
 3071                )
 3072        ˇ
 3073        ˇ    )
 3074        );
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        ˇ
 3079        const a: B = (
 3080            c(
 3081                d(
 3082                    ˇ
 3083                )
 3084                ˇ
 3085            ˇ)
 3086        );
 3087    "});
 3088    // test when all cursors are at suggested indent then tab is inserted
 3089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3090    cx.assert_editor_state(indoc! {"
 3091            ˇ
 3092        const a: B = (
 3093            c(
 3094                d(
 3095                        ˇ
 3096                )
 3097                    ˇ
 3098                ˇ)
 3099        );
 3100    "});
 3101
 3102    // test when current indent is less than suggested indent,
 3103    // we adjust line to match suggested indent and move cursor to it
 3104    //
 3105    // when no other cursor is at word boundary, all of them should move
 3106    cx.set_state(indoc! {"
 3107        const a: B = (
 3108            c(
 3109                d(
 3110        ˇ
 3111        ˇ   )
 3112        ˇ   )
 3113        );
 3114    "});
 3115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117        const a: B = (
 3118            c(
 3119                d(
 3120                    ˇ
 3121                ˇ)
 3122            ˇ)
 3123        );
 3124    "});
 3125
 3126    // test when current indent is less than suggested indent,
 3127    // we adjust line to match suggested indent and move cursor to it
 3128    //
 3129    // when some other cursor is at word boundary, it should not move
 3130    cx.set_state(indoc! {"
 3131        const a: B = (
 3132            c(
 3133                d(
 3134        ˇ
 3135        ˇ   )
 3136           ˇ)
 3137        );
 3138    "});
 3139    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3140    cx.assert_editor_state(indoc! {"
 3141        const a: B = (
 3142            c(
 3143                d(
 3144                    ˇ
 3145                ˇ)
 3146            ˇ)
 3147        );
 3148    "});
 3149
 3150    // test when current indent is more than suggested indent,
 3151    // we just move cursor to current indent instead of suggested indent
 3152    //
 3153    // when no other cursor is at word boundary, all of them should move
 3154    cx.set_state(indoc! {"
 3155        const a: B = (
 3156            c(
 3157                d(
 3158        ˇ
 3159        ˇ                )
 3160        ˇ   )
 3161        );
 3162    "});
 3163    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3164    cx.assert_editor_state(indoc! {"
 3165        const a: B = (
 3166            c(
 3167                d(
 3168                    ˇ
 3169                        ˇ)
 3170            ˇ)
 3171        );
 3172    "});
 3173    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3174    cx.assert_editor_state(indoc! {"
 3175        const a: B = (
 3176            c(
 3177                d(
 3178                        ˇ
 3179                            ˇ)
 3180                ˇ)
 3181        );
 3182    "});
 3183
 3184    // test when current indent is more than suggested indent,
 3185    // we just move cursor to current indent instead of suggested indent
 3186    //
 3187    // when some other cursor is at word boundary, it doesn't move
 3188    cx.set_state(indoc! {"
 3189        const a: B = (
 3190            c(
 3191                d(
 3192        ˇ
 3193        ˇ                )
 3194            ˇ)
 3195        );
 3196    "});
 3197    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3198    cx.assert_editor_state(indoc! {"
 3199        const a: B = (
 3200            c(
 3201                d(
 3202                    ˇ
 3203                        ˇ)
 3204            ˇ)
 3205        );
 3206    "});
 3207
 3208    // handle auto-indent when there are multiple cursors on the same line
 3209    cx.set_state(indoc! {"
 3210        const a: B = (
 3211            c(
 3212        ˇ    ˇ
 3213        ˇ    )
 3214        );
 3215    "});
 3216    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3217    cx.assert_editor_state(indoc! {"
 3218        const a: B = (
 3219            c(
 3220                ˇ
 3221            ˇ)
 3222        );
 3223    "});
 3224}
 3225
 3226#[gpui::test]
 3227async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3228    init_test(cx, |settings| {
 3229        settings.defaults.tab_size = NonZeroU32::new(3)
 3230    });
 3231
 3232    let mut cx = EditorTestContext::new(cx).await;
 3233    cx.set_state(indoc! {"
 3234         ˇ
 3235        \t ˇ
 3236        \t  ˇ
 3237        \t   ˇ
 3238         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3239    "});
 3240
 3241    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3242    cx.assert_editor_state(indoc! {"
 3243           ˇ
 3244        \t   ˇ
 3245        \t   ˇ
 3246        \t      ˇ
 3247         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3248    "});
 3249}
 3250
 3251#[gpui::test]
 3252async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3253    init_test(cx, |settings| {
 3254        settings.defaults.tab_size = NonZeroU32::new(4)
 3255    });
 3256
 3257    let language = Arc::new(
 3258        Language::new(
 3259            LanguageConfig::default(),
 3260            Some(tree_sitter_rust::LANGUAGE.into()),
 3261        )
 3262        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3263        .unwrap(),
 3264    );
 3265
 3266    let mut cx = EditorTestContext::new(cx).await;
 3267    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3268    cx.set_state(indoc! {"
 3269        fn a() {
 3270            if b {
 3271        \t ˇc
 3272            }
 3273        }
 3274    "});
 3275
 3276    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3277    cx.assert_editor_state(indoc! {"
 3278        fn a() {
 3279            if b {
 3280                ˇc
 3281            }
 3282        }
 3283    "});
 3284}
 3285
 3286#[gpui::test]
 3287async fn test_indent_outdent(cx: &mut TestAppContext) {
 3288    init_test(cx, |settings| {
 3289        settings.defaults.tab_size = NonZeroU32::new(4);
 3290    });
 3291
 3292    let mut cx = EditorTestContext::new(cx).await;
 3293
 3294    cx.set_state(indoc! {"
 3295          «oneˇ» «twoˇ»
 3296        three
 3297         four
 3298    "});
 3299    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3300    cx.assert_editor_state(indoc! {"
 3301            «oneˇ» «twoˇ»
 3302        three
 3303         four
 3304    "});
 3305
 3306    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3307    cx.assert_editor_state(indoc! {"
 3308        «oneˇ» «twoˇ»
 3309        three
 3310         four
 3311    "});
 3312
 3313    // select across line ending
 3314    cx.set_state(indoc! {"
 3315        one two
 3316        t«hree
 3317        ˇ» four
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        one two
 3322            t«hree
 3323        ˇ» four
 3324    "});
 3325
 3326    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3327    cx.assert_editor_state(indoc! {"
 3328        one two
 3329        t«hree
 3330        ˇ» four
 3331    "});
 3332
 3333    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3334    cx.set_state(indoc! {"
 3335        one two
 3336        ˇthree
 3337            four
 3338    "});
 3339    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3340    cx.assert_editor_state(indoc! {"
 3341        one two
 3342            ˇthree
 3343            four
 3344    "});
 3345
 3346    cx.set_state(indoc! {"
 3347        one two
 3348        ˇ    three
 3349            four
 3350    "});
 3351    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3352    cx.assert_editor_state(indoc! {"
 3353        one two
 3354        ˇthree
 3355            four
 3356    "});
 3357}
 3358
 3359#[gpui::test]
 3360async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3361    init_test(cx, |settings| {
 3362        settings.defaults.hard_tabs = Some(true);
 3363    });
 3364
 3365    let mut cx = EditorTestContext::new(cx).await;
 3366
 3367    // select two ranges on one line
 3368    cx.set_state(indoc! {"
 3369        «oneˇ» «twoˇ»
 3370        three
 3371        four
 3372    "});
 3373    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3374    cx.assert_editor_state(indoc! {"
 3375        \t«oneˇ» «twoˇ»
 3376        three
 3377        four
 3378    "});
 3379    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3380    cx.assert_editor_state(indoc! {"
 3381        \t\t«oneˇ» «twoˇ»
 3382        three
 3383        four
 3384    "});
 3385    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3386    cx.assert_editor_state(indoc! {"
 3387        \t«oneˇ» «twoˇ»
 3388        three
 3389        four
 3390    "});
 3391    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3392    cx.assert_editor_state(indoc! {"
 3393        «oneˇ» «twoˇ»
 3394        three
 3395        four
 3396    "});
 3397
 3398    // select across a line ending
 3399    cx.set_state(indoc! {"
 3400        one two
 3401        t«hree
 3402        ˇ»four
 3403    "});
 3404    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3405    cx.assert_editor_state(indoc! {"
 3406        one two
 3407        \tt«hree
 3408        ˇ»four
 3409    "});
 3410    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3411    cx.assert_editor_state(indoc! {"
 3412        one two
 3413        \t\tt«hree
 3414        ˇ»four
 3415    "});
 3416    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3417    cx.assert_editor_state(indoc! {"
 3418        one two
 3419        \tt«hree
 3420        ˇ»four
 3421    "});
 3422    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3423    cx.assert_editor_state(indoc! {"
 3424        one two
 3425        t«hree
 3426        ˇ»four
 3427    "});
 3428
 3429    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3430    cx.set_state(indoc! {"
 3431        one two
 3432        ˇthree
 3433        four
 3434    "});
 3435    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3436    cx.assert_editor_state(indoc! {"
 3437        one two
 3438        ˇthree
 3439        four
 3440    "});
 3441    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3442    cx.assert_editor_state(indoc! {"
 3443        one two
 3444        \tˇthree
 3445        four
 3446    "});
 3447    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3448    cx.assert_editor_state(indoc! {"
 3449        one two
 3450        ˇthree
 3451        four
 3452    "});
 3453}
 3454
 3455#[gpui::test]
 3456fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3457    init_test(cx, |settings| {
 3458        settings.languages.extend([
 3459            (
 3460                "TOML".into(),
 3461                LanguageSettingsContent {
 3462                    tab_size: NonZeroU32::new(2),
 3463                    ..Default::default()
 3464                },
 3465            ),
 3466            (
 3467                "Rust".into(),
 3468                LanguageSettingsContent {
 3469                    tab_size: NonZeroU32::new(4),
 3470                    ..Default::default()
 3471                },
 3472            ),
 3473        ]);
 3474    });
 3475
 3476    let toml_language = Arc::new(Language::new(
 3477        LanguageConfig {
 3478            name: "TOML".into(),
 3479            ..Default::default()
 3480        },
 3481        None,
 3482    ));
 3483    let rust_language = Arc::new(Language::new(
 3484        LanguageConfig {
 3485            name: "Rust".into(),
 3486            ..Default::default()
 3487        },
 3488        None,
 3489    ));
 3490
 3491    let toml_buffer =
 3492        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3493    let rust_buffer =
 3494        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3495    let multibuffer = cx.new(|cx| {
 3496        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3497        multibuffer.push_excerpts(
 3498            toml_buffer.clone(),
 3499            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3500            cx,
 3501        );
 3502        multibuffer.push_excerpts(
 3503            rust_buffer.clone(),
 3504            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3505            cx,
 3506        );
 3507        multibuffer
 3508    });
 3509
 3510    cx.add_window(|window, cx| {
 3511        let mut editor = build_editor(multibuffer, window, cx);
 3512
 3513        assert_eq!(
 3514            editor.text(cx),
 3515            indoc! {"
 3516                a = 1
 3517                b = 2
 3518
 3519                const c: usize = 3;
 3520            "}
 3521        );
 3522
 3523        select_ranges(
 3524            &mut editor,
 3525            indoc! {"
 3526                «aˇ» = 1
 3527                b = 2
 3528
 3529                «const c:ˇ» usize = 3;
 3530            "},
 3531            window,
 3532            cx,
 3533        );
 3534
 3535        editor.tab(&Tab, window, cx);
 3536        assert_text_with_selections(
 3537            &mut editor,
 3538            indoc! {"
 3539                  «aˇ» = 1
 3540                b = 2
 3541
 3542                    «const c:ˇ» usize = 3;
 3543            "},
 3544            cx,
 3545        );
 3546        editor.backtab(&Backtab, window, cx);
 3547        assert_text_with_selections(
 3548            &mut editor,
 3549            indoc! {"
 3550                «aˇ» = 1
 3551                b = 2
 3552
 3553                «const c:ˇ» usize = 3;
 3554            "},
 3555            cx,
 3556        );
 3557
 3558        editor
 3559    });
 3560}
 3561
 3562#[gpui::test]
 3563async fn test_backspace(cx: &mut TestAppContext) {
 3564    init_test(cx, |_| {});
 3565
 3566    let mut cx = EditorTestContext::new(cx).await;
 3567
 3568    // Basic backspace
 3569    cx.set_state(indoc! {"
 3570        onˇe two three
 3571        fou«rˇ» five six
 3572        seven «ˇeight nine
 3573        »ten
 3574    "});
 3575    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3576    cx.assert_editor_state(indoc! {"
 3577        oˇe two three
 3578        fouˇ five six
 3579        seven ˇten
 3580    "});
 3581
 3582    // Test backspace inside and around indents
 3583    cx.set_state(indoc! {"
 3584        zero
 3585            ˇone
 3586                ˇtwo
 3587            ˇ ˇ ˇ  three
 3588        ˇ  ˇ  four
 3589    "});
 3590    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3591    cx.assert_editor_state(indoc! {"
 3592        zero
 3593        ˇone
 3594            ˇtwo
 3595        ˇ  threeˇ  four
 3596    "});
 3597}
 3598
 3599#[gpui::test]
 3600async fn test_delete(cx: &mut TestAppContext) {
 3601    init_test(cx, |_| {});
 3602
 3603    let mut cx = EditorTestContext::new(cx).await;
 3604    cx.set_state(indoc! {"
 3605        onˇe two three
 3606        fou«rˇ» five six
 3607        seven «ˇeight nine
 3608        »ten
 3609    "});
 3610    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3611    cx.assert_editor_state(indoc! {"
 3612        onˇ two three
 3613        fouˇ five six
 3614        seven ˇten
 3615    "});
 3616}
 3617
 3618#[gpui::test]
 3619fn test_delete_line(cx: &mut TestAppContext) {
 3620    init_test(cx, |_| {});
 3621
 3622    let editor = cx.add_window(|window, cx| {
 3623        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3624        build_editor(buffer, window, cx)
 3625    });
 3626    _ = editor.update(cx, |editor, window, cx| {
 3627        editor.change_selections(None, window, cx, |s| {
 3628            s.select_display_ranges([
 3629                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3630                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3631                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3632            ])
 3633        });
 3634        editor.delete_line(&DeleteLine, window, cx);
 3635        assert_eq!(editor.display_text(cx), "ghi");
 3636        assert_eq!(
 3637            editor.selections.display_ranges(cx),
 3638            vec![
 3639                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3640                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3641            ]
 3642        );
 3643    });
 3644
 3645    let editor = cx.add_window(|window, cx| {
 3646        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3647        build_editor(buffer, window, cx)
 3648    });
 3649    _ = editor.update(cx, |editor, window, cx| {
 3650        editor.change_selections(None, window, cx, |s| {
 3651            s.select_display_ranges([
 3652                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3653            ])
 3654        });
 3655        editor.delete_line(&DeleteLine, window, cx);
 3656        assert_eq!(editor.display_text(cx), "ghi\n");
 3657        assert_eq!(
 3658            editor.selections.display_ranges(cx),
 3659            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3660        );
 3661    });
 3662}
 3663
 3664#[gpui::test]
 3665fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3666    init_test(cx, |_| {});
 3667
 3668    cx.add_window(|window, cx| {
 3669        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3670        let mut editor = build_editor(buffer.clone(), window, cx);
 3671        let buffer = buffer.read(cx).as_singleton().unwrap();
 3672
 3673        assert_eq!(
 3674            editor.selections.ranges::<Point>(cx),
 3675            &[Point::new(0, 0)..Point::new(0, 0)]
 3676        );
 3677
 3678        // When on single line, replace newline at end by space
 3679        editor.join_lines(&JoinLines, window, cx);
 3680        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3681        assert_eq!(
 3682            editor.selections.ranges::<Point>(cx),
 3683            &[Point::new(0, 3)..Point::new(0, 3)]
 3684        );
 3685
 3686        // When multiple lines are selected, remove newlines that are spanned by the selection
 3687        editor.change_selections(None, window, cx, |s| {
 3688            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3689        });
 3690        editor.join_lines(&JoinLines, window, cx);
 3691        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3692        assert_eq!(
 3693            editor.selections.ranges::<Point>(cx),
 3694            &[Point::new(0, 11)..Point::new(0, 11)]
 3695        );
 3696
 3697        // Undo should be transactional
 3698        editor.undo(&Undo, window, cx);
 3699        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3700        assert_eq!(
 3701            editor.selections.ranges::<Point>(cx),
 3702            &[Point::new(0, 5)..Point::new(2, 2)]
 3703        );
 3704
 3705        // When joining an empty line don't insert a space
 3706        editor.change_selections(None, window, cx, |s| {
 3707            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3708        });
 3709        editor.join_lines(&JoinLines, window, cx);
 3710        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3711        assert_eq!(
 3712            editor.selections.ranges::<Point>(cx),
 3713            [Point::new(2, 3)..Point::new(2, 3)]
 3714        );
 3715
 3716        // We can remove trailing newlines
 3717        editor.join_lines(&JoinLines, window, cx);
 3718        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3719        assert_eq!(
 3720            editor.selections.ranges::<Point>(cx),
 3721            [Point::new(2, 3)..Point::new(2, 3)]
 3722        );
 3723
 3724        // We don't blow up on the last line
 3725        editor.join_lines(&JoinLines, window, cx);
 3726        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3727        assert_eq!(
 3728            editor.selections.ranges::<Point>(cx),
 3729            [Point::new(2, 3)..Point::new(2, 3)]
 3730        );
 3731
 3732        // reset to test indentation
 3733        editor.buffer.update(cx, |buffer, cx| {
 3734            buffer.edit(
 3735                [
 3736                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3737                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3738                ],
 3739                None,
 3740                cx,
 3741            )
 3742        });
 3743
 3744        // We remove any leading spaces
 3745        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3746        editor.change_selections(None, window, cx, |s| {
 3747            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3748        });
 3749        editor.join_lines(&JoinLines, window, cx);
 3750        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3751
 3752        // We don't insert a space for a line containing only spaces
 3753        editor.join_lines(&JoinLines, window, cx);
 3754        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3755
 3756        // We ignore any leading tabs
 3757        editor.join_lines(&JoinLines, window, cx);
 3758        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3759
 3760        editor
 3761    });
 3762}
 3763
 3764#[gpui::test]
 3765fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3766    init_test(cx, |_| {});
 3767
 3768    cx.add_window(|window, cx| {
 3769        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3770        let mut editor = build_editor(buffer.clone(), window, cx);
 3771        let buffer = buffer.read(cx).as_singleton().unwrap();
 3772
 3773        editor.change_selections(None, window, cx, |s| {
 3774            s.select_ranges([
 3775                Point::new(0, 2)..Point::new(1, 1),
 3776                Point::new(1, 2)..Point::new(1, 2),
 3777                Point::new(3, 1)..Point::new(3, 2),
 3778            ])
 3779        });
 3780
 3781        editor.join_lines(&JoinLines, window, cx);
 3782        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3783
 3784        assert_eq!(
 3785            editor.selections.ranges::<Point>(cx),
 3786            [
 3787                Point::new(0, 7)..Point::new(0, 7),
 3788                Point::new(1, 3)..Point::new(1, 3)
 3789            ]
 3790        );
 3791        editor
 3792    });
 3793}
 3794
 3795#[gpui::test]
 3796async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3797    init_test(cx, |_| {});
 3798
 3799    let mut cx = EditorTestContext::new(cx).await;
 3800
 3801    let diff_base = r#"
 3802        Line 0
 3803        Line 1
 3804        Line 2
 3805        Line 3
 3806        "#
 3807    .unindent();
 3808
 3809    cx.set_state(
 3810        &r#"
 3811        ˇLine 0
 3812        Line 1
 3813        Line 2
 3814        Line 3
 3815        "#
 3816        .unindent(),
 3817    );
 3818
 3819    cx.set_head_text(&diff_base);
 3820    executor.run_until_parked();
 3821
 3822    // Join lines
 3823    cx.update_editor(|editor, window, cx| {
 3824        editor.join_lines(&JoinLines, window, cx);
 3825    });
 3826    executor.run_until_parked();
 3827
 3828    cx.assert_editor_state(
 3829        &r#"
 3830        Line 0ˇ Line 1
 3831        Line 2
 3832        Line 3
 3833        "#
 3834        .unindent(),
 3835    );
 3836    // Join again
 3837    cx.update_editor(|editor, window, cx| {
 3838        editor.join_lines(&JoinLines, window, cx);
 3839    });
 3840    executor.run_until_parked();
 3841
 3842    cx.assert_editor_state(
 3843        &r#"
 3844        Line 0 Line 1ˇ Line 2
 3845        Line 3
 3846        "#
 3847        .unindent(),
 3848    );
 3849}
 3850
 3851#[gpui::test]
 3852async fn test_custom_newlines_cause_no_false_positive_diffs(
 3853    executor: BackgroundExecutor,
 3854    cx: &mut TestAppContext,
 3855) {
 3856    init_test(cx, |_| {});
 3857    let mut cx = EditorTestContext::new(cx).await;
 3858    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3859    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3860    executor.run_until_parked();
 3861
 3862    cx.update_editor(|editor, window, cx| {
 3863        let snapshot = editor.snapshot(window, cx);
 3864        assert_eq!(
 3865            snapshot
 3866                .buffer_snapshot
 3867                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3868                .collect::<Vec<_>>(),
 3869            Vec::new(),
 3870            "Should not have any diffs for files with custom newlines"
 3871        );
 3872    });
 3873}
 3874
 3875#[gpui::test]
 3876async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3877    init_test(cx, |_| {});
 3878
 3879    let mut cx = EditorTestContext::new(cx).await;
 3880
 3881    // Test sort_lines_case_insensitive()
 3882    cx.set_state(indoc! {"
 3883        «z
 3884        y
 3885        x
 3886        Z
 3887        Y
 3888        Xˇ»
 3889    "});
 3890    cx.update_editor(|e, window, cx| {
 3891        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3892    });
 3893    cx.assert_editor_state(indoc! {"
 3894        «x
 3895        X
 3896        y
 3897        Y
 3898        z
 3899        Zˇ»
 3900    "});
 3901
 3902    // Test reverse_lines()
 3903    cx.set_state(indoc! {"
 3904        «5
 3905        4
 3906        3
 3907        2
 3908        1ˇ»
 3909    "});
 3910    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3911    cx.assert_editor_state(indoc! {"
 3912        «1
 3913        2
 3914        3
 3915        4
 3916        5ˇ»
 3917    "});
 3918
 3919    // Skip testing shuffle_line()
 3920
 3921    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3922    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3923
 3924    // Don't manipulate when cursor is on single line, but expand the selection
 3925    cx.set_state(indoc! {"
 3926        ddˇdd
 3927        ccc
 3928        bb
 3929        a
 3930    "});
 3931    cx.update_editor(|e, window, cx| {
 3932        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3933    });
 3934    cx.assert_editor_state(indoc! {"
 3935        «ddddˇ»
 3936        ccc
 3937        bb
 3938        a
 3939    "});
 3940
 3941    // Basic manipulate case
 3942    // Start selection moves to column 0
 3943    // End of selection shrinks to fit shorter line
 3944    cx.set_state(indoc! {"
 3945        dd«d
 3946        ccc
 3947        bb
 3948        aaaaaˇ»
 3949    "});
 3950    cx.update_editor(|e, window, cx| {
 3951        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3952    });
 3953    cx.assert_editor_state(indoc! {"
 3954        «aaaaa
 3955        bb
 3956        ccc
 3957        dddˇ»
 3958    "});
 3959
 3960    // Manipulate case with newlines
 3961    cx.set_state(indoc! {"
 3962        dd«d
 3963        ccc
 3964
 3965        bb
 3966        aaaaa
 3967
 3968        ˇ»
 3969    "});
 3970    cx.update_editor(|e, window, cx| {
 3971        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3972    });
 3973    cx.assert_editor_state(indoc! {"
 3974        «
 3975
 3976        aaaaa
 3977        bb
 3978        ccc
 3979        dddˇ»
 3980
 3981    "});
 3982
 3983    // Adding new line
 3984    cx.set_state(indoc! {"
 3985        aa«a
 3986        bbˇ»b
 3987    "});
 3988    cx.update_editor(|e, window, cx| {
 3989        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 3990    });
 3991    cx.assert_editor_state(indoc! {"
 3992        «aaa
 3993        bbb
 3994        added_lineˇ»
 3995    "});
 3996
 3997    // Removing line
 3998    cx.set_state(indoc! {"
 3999        aa«a
 4000        bbbˇ»
 4001    "});
 4002    cx.update_editor(|e, window, cx| {
 4003        e.manipulate_lines(window, cx, |lines| {
 4004            lines.pop();
 4005        })
 4006    });
 4007    cx.assert_editor_state(indoc! {"
 4008        «aaaˇ»
 4009    "});
 4010
 4011    // Removing all lines
 4012    cx.set_state(indoc! {"
 4013        aa«a
 4014        bbbˇ»
 4015    "});
 4016    cx.update_editor(|e, window, cx| {
 4017        e.manipulate_lines(window, cx, |lines| {
 4018            lines.drain(..);
 4019        })
 4020    });
 4021    cx.assert_editor_state(indoc! {"
 4022        ˇ
 4023    "});
 4024}
 4025
 4026#[gpui::test]
 4027async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4028    init_test(cx, |_| {});
 4029
 4030    let mut cx = EditorTestContext::new(cx).await;
 4031
 4032    // Consider continuous selection as single selection
 4033    cx.set_state(indoc! {"
 4034        Aaa«aa
 4035        cˇ»c«c
 4036        bb
 4037        aaaˇ»aa
 4038    "});
 4039    cx.update_editor(|e, window, cx| {
 4040        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4041    });
 4042    cx.assert_editor_state(indoc! {"
 4043        «Aaaaa
 4044        ccc
 4045        bb
 4046        aaaaaˇ»
 4047    "});
 4048
 4049    cx.set_state(indoc! {"
 4050        Aaa«aa
 4051        cˇ»c«c
 4052        bb
 4053        aaaˇ»aa
 4054    "});
 4055    cx.update_editor(|e, window, cx| {
 4056        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4057    });
 4058    cx.assert_editor_state(indoc! {"
 4059        «Aaaaa
 4060        ccc
 4061        bbˇ»
 4062    "});
 4063
 4064    // Consider non continuous selection as distinct dedup operations
 4065    cx.set_state(indoc! {"
 4066        «aaaaa
 4067        bb
 4068        aaaaa
 4069        aaaaaˇ»
 4070
 4071        aaa«aaˇ»
 4072    "});
 4073    cx.update_editor(|e, window, cx| {
 4074        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4075    });
 4076    cx.assert_editor_state(indoc! {"
 4077        «aaaaa
 4078        bbˇ»
 4079
 4080        «aaaaaˇ»
 4081    "});
 4082}
 4083
 4084#[gpui::test]
 4085async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4086    init_test(cx, |_| {});
 4087
 4088    let mut cx = EditorTestContext::new(cx).await;
 4089
 4090    cx.set_state(indoc! {"
 4091        «Aaa
 4092        aAa
 4093        Aaaˇ»
 4094    "});
 4095    cx.update_editor(|e, window, cx| {
 4096        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4097    });
 4098    cx.assert_editor_state(indoc! {"
 4099        «Aaa
 4100        aAaˇ»
 4101    "});
 4102
 4103    cx.set_state(indoc! {"
 4104        «Aaa
 4105        aAa
 4106        aaAˇ»
 4107    "});
 4108    cx.update_editor(|e, window, cx| {
 4109        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4110    });
 4111    cx.assert_editor_state(indoc! {"
 4112        «Aaaˇ»
 4113    "});
 4114}
 4115
 4116#[gpui::test]
 4117async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4118    init_test(cx, |_| {});
 4119
 4120    let mut cx = EditorTestContext::new(cx).await;
 4121
 4122    // Manipulate with multiple selections on a single line
 4123    cx.set_state(indoc! {"
 4124        dd«dd
 4125        cˇ»c«c
 4126        bb
 4127        aaaˇ»aa
 4128    "});
 4129    cx.update_editor(|e, window, cx| {
 4130        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4131    });
 4132    cx.assert_editor_state(indoc! {"
 4133        «aaaaa
 4134        bb
 4135        ccc
 4136        ddddˇ»
 4137    "});
 4138
 4139    // Manipulate with multiple disjoin selections
 4140    cx.set_state(indoc! {"
 4141 4142        4
 4143        3
 4144        2
 4145        1ˇ»
 4146
 4147        dd«dd
 4148        ccc
 4149        bb
 4150        aaaˇ»aa
 4151    "});
 4152    cx.update_editor(|e, window, cx| {
 4153        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4154    });
 4155    cx.assert_editor_state(indoc! {"
 4156        «1
 4157        2
 4158        3
 4159        4
 4160        5ˇ»
 4161
 4162        «aaaaa
 4163        bb
 4164        ccc
 4165        ddddˇ»
 4166    "});
 4167
 4168    // Adding lines on each selection
 4169    cx.set_state(indoc! {"
 4170 4171        1ˇ»
 4172
 4173        bb«bb
 4174        aaaˇ»aa
 4175    "});
 4176    cx.update_editor(|e, window, cx| {
 4177        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4178    });
 4179    cx.assert_editor_state(indoc! {"
 4180        «2
 4181        1
 4182        added lineˇ»
 4183
 4184        «bbbb
 4185        aaaaa
 4186        added lineˇ»
 4187    "});
 4188
 4189    // Removing lines on each selection
 4190    cx.set_state(indoc! {"
 4191 4192        1ˇ»
 4193
 4194        bb«bb
 4195        aaaˇ»aa
 4196    "});
 4197    cx.update_editor(|e, window, cx| {
 4198        e.manipulate_lines(window, cx, |lines| {
 4199            lines.pop();
 4200        })
 4201    });
 4202    cx.assert_editor_state(indoc! {"
 4203        «2ˇ»
 4204
 4205        «bbbbˇ»
 4206    "});
 4207}
 4208
 4209#[gpui::test]
 4210async fn test_toggle_case(cx: &mut TestAppContext) {
 4211    init_test(cx, |_| {});
 4212
 4213    let mut cx = EditorTestContext::new(cx).await;
 4214
 4215    // If all lower case -> upper case
 4216    cx.set_state(indoc! {"
 4217        «hello worldˇ»
 4218    "});
 4219    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4220    cx.assert_editor_state(indoc! {"
 4221        «HELLO WORLDˇ»
 4222    "});
 4223
 4224    // If all upper case -> lower case
 4225    cx.set_state(indoc! {"
 4226        «HELLO WORLDˇ»
 4227    "});
 4228    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4229    cx.assert_editor_state(indoc! {"
 4230        «hello worldˇ»
 4231    "});
 4232
 4233    // If any upper case characters are identified -> lower case
 4234    // This matches JetBrains IDEs
 4235    cx.set_state(indoc! {"
 4236        «hEllo worldˇ»
 4237    "});
 4238    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4239    cx.assert_editor_state(indoc! {"
 4240        «hello worldˇ»
 4241    "});
 4242}
 4243
 4244#[gpui::test]
 4245async fn test_manipulate_text(cx: &mut TestAppContext) {
 4246    init_test(cx, |_| {});
 4247
 4248    let mut cx = EditorTestContext::new(cx).await;
 4249
 4250    // Test convert_to_upper_case()
 4251    cx.set_state(indoc! {"
 4252        «hello worldˇ»
 4253    "});
 4254    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4255    cx.assert_editor_state(indoc! {"
 4256        «HELLO WORLDˇ»
 4257    "});
 4258
 4259    // Test convert_to_lower_case()
 4260    cx.set_state(indoc! {"
 4261        «HELLO WORLDˇ»
 4262    "});
 4263    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4264    cx.assert_editor_state(indoc! {"
 4265        «hello worldˇ»
 4266    "});
 4267
 4268    // Test multiple line, single selection case
 4269    cx.set_state(indoc! {"
 4270        «The quick brown
 4271        fox jumps over
 4272        the lazy dogˇ»
 4273    "});
 4274    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4275    cx.assert_editor_state(indoc! {"
 4276        «The Quick Brown
 4277        Fox Jumps Over
 4278        The Lazy Dogˇ»
 4279    "});
 4280
 4281    // Test multiple line, single selection case
 4282    cx.set_state(indoc! {"
 4283        «The quick brown
 4284        fox jumps over
 4285        the lazy dogˇ»
 4286    "});
 4287    cx.update_editor(|e, window, cx| {
 4288        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4289    });
 4290    cx.assert_editor_state(indoc! {"
 4291        «TheQuickBrown
 4292        FoxJumpsOver
 4293        TheLazyDogˇ»
 4294    "});
 4295
 4296    // From here on out, test more complex cases of manipulate_text()
 4297
 4298    // Test no selection case - should affect words cursors are in
 4299    // Cursor at beginning, middle, and end of word
 4300    cx.set_state(indoc! {"
 4301        ˇhello big beauˇtiful worldˇ
 4302    "});
 4303    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4304    cx.assert_editor_state(indoc! {"
 4305        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4306    "});
 4307
 4308    // Test multiple selections on a single line and across multiple lines
 4309    cx.set_state(indoc! {"
 4310        «Theˇ» quick «brown
 4311        foxˇ» jumps «overˇ»
 4312        the «lazyˇ» dog
 4313    "});
 4314    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4315    cx.assert_editor_state(indoc! {"
 4316        «THEˇ» quick «BROWN
 4317        FOXˇ» jumps «OVERˇ»
 4318        the «LAZYˇ» dog
 4319    "});
 4320
 4321    // Test case where text length grows
 4322    cx.set_state(indoc! {"
 4323        «tschüߡ»
 4324    "});
 4325    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4326    cx.assert_editor_state(indoc! {"
 4327        «TSCHÜSSˇ»
 4328    "});
 4329
 4330    // Test to make sure we don't crash when text shrinks
 4331    cx.set_state(indoc! {"
 4332        aaa_bbbˇ
 4333    "});
 4334    cx.update_editor(|e, window, cx| {
 4335        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4336    });
 4337    cx.assert_editor_state(indoc! {"
 4338        «aaaBbbˇ»
 4339    "});
 4340
 4341    // Test to make sure we all aware of the fact that each word can grow and shrink
 4342    // Final selections should be aware of this fact
 4343    cx.set_state(indoc! {"
 4344        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4345    "});
 4346    cx.update_editor(|e, window, cx| {
 4347        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4348    });
 4349    cx.assert_editor_state(indoc! {"
 4350        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4351    "});
 4352
 4353    cx.set_state(indoc! {"
 4354        «hElLo, WoRld!ˇ»
 4355    "});
 4356    cx.update_editor(|e, window, cx| {
 4357        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4358    });
 4359    cx.assert_editor_state(indoc! {"
 4360        «HeLlO, wOrLD!ˇ»
 4361    "});
 4362}
 4363
 4364#[gpui::test]
 4365fn test_duplicate_line(cx: &mut TestAppContext) {
 4366    init_test(cx, |_| {});
 4367
 4368    let editor = cx.add_window(|window, cx| {
 4369        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4370        build_editor(buffer, window, cx)
 4371    });
 4372    _ = editor.update(cx, |editor, window, cx| {
 4373        editor.change_selections(None, window, cx, |s| {
 4374            s.select_display_ranges([
 4375                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4376                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4377                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4378                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4379            ])
 4380        });
 4381        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4382        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4383        assert_eq!(
 4384            editor.selections.display_ranges(cx),
 4385            vec![
 4386                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4387                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4388                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4389                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4390            ]
 4391        );
 4392    });
 4393
 4394    let editor = cx.add_window(|window, cx| {
 4395        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4396        build_editor(buffer, window, cx)
 4397    });
 4398    _ = editor.update(cx, |editor, window, cx| {
 4399        editor.change_selections(None, window, cx, |s| {
 4400            s.select_display_ranges([
 4401                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4402                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4403            ])
 4404        });
 4405        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4406        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4407        assert_eq!(
 4408            editor.selections.display_ranges(cx),
 4409            vec![
 4410                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4411                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4412            ]
 4413        );
 4414    });
 4415
 4416    // With `move_upwards` the selections stay in place, except for
 4417    // the lines inserted above them
 4418    let editor = cx.add_window(|window, cx| {
 4419        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4420        build_editor(buffer, window, cx)
 4421    });
 4422    _ = editor.update(cx, |editor, window, cx| {
 4423        editor.change_selections(None, window, cx, |s| {
 4424            s.select_display_ranges([
 4425                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4426                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4427                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4428                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4429            ])
 4430        });
 4431        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4432        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4433        assert_eq!(
 4434            editor.selections.display_ranges(cx),
 4435            vec![
 4436                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4437                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4438                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4439                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4440            ]
 4441        );
 4442    });
 4443
 4444    let editor = cx.add_window(|window, cx| {
 4445        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4446        build_editor(buffer, window, cx)
 4447    });
 4448    _ = editor.update(cx, |editor, window, cx| {
 4449        editor.change_selections(None, window, cx, |s| {
 4450            s.select_display_ranges([
 4451                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4452                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4453            ])
 4454        });
 4455        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4456        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4457        assert_eq!(
 4458            editor.selections.display_ranges(cx),
 4459            vec![
 4460                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4461                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4462            ]
 4463        );
 4464    });
 4465
 4466    let editor = cx.add_window(|window, cx| {
 4467        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4468        build_editor(buffer, window, cx)
 4469    });
 4470    _ = editor.update(cx, |editor, window, cx| {
 4471        editor.change_selections(None, window, cx, |s| {
 4472            s.select_display_ranges([
 4473                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4474                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4475            ])
 4476        });
 4477        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4478        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4479        assert_eq!(
 4480            editor.selections.display_ranges(cx),
 4481            vec![
 4482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4483                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4484            ]
 4485        );
 4486    });
 4487}
 4488
 4489#[gpui::test]
 4490fn test_move_line_up_down(cx: &mut TestAppContext) {
 4491    init_test(cx, |_| {});
 4492
 4493    let editor = cx.add_window(|window, cx| {
 4494        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4495        build_editor(buffer, window, cx)
 4496    });
 4497    _ = editor.update(cx, |editor, window, cx| {
 4498        editor.fold_creases(
 4499            vec![
 4500                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4501                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4502                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4503            ],
 4504            true,
 4505            window,
 4506            cx,
 4507        );
 4508        editor.change_selections(None, window, cx, |s| {
 4509            s.select_display_ranges([
 4510                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4511                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4512                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4513                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4514            ])
 4515        });
 4516        assert_eq!(
 4517            editor.display_text(cx),
 4518            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4519        );
 4520
 4521        editor.move_line_up(&MoveLineUp, window, cx);
 4522        assert_eq!(
 4523            editor.display_text(cx),
 4524            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4525        );
 4526        assert_eq!(
 4527            editor.selections.display_ranges(cx),
 4528            vec![
 4529                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4530                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4531                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4532                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4533            ]
 4534        );
 4535    });
 4536
 4537    _ = editor.update(cx, |editor, window, cx| {
 4538        editor.move_line_down(&MoveLineDown, window, cx);
 4539        assert_eq!(
 4540            editor.display_text(cx),
 4541            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4542        );
 4543        assert_eq!(
 4544            editor.selections.display_ranges(cx),
 4545            vec![
 4546                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4547                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4548                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4549                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4550            ]
 4551        );
 4552    });
 4553
 4554    _ = editor.update(cx, |editor, window, cx| {
 4555        editor.move_line_down(&MoveLineDown, window, cx);
 4556        assert_eq!(
 4557            editor.display_text(cx),
 4558            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4559        );
 4560        assert_eq!(
 4561            editor.selections.display_ranges(cx),
 4562            vec![
 4563                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4564                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4565                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4566                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4567            ]
 4568        );
 4569    });
 4570
 4571    _ = editor.update(cx, |editor, window, cx| {
 4572        editor.move_line_up(&MoveLineUp, window, cx);
 4573        assert_eq!(
 4574            editor.display_text(cx),
 4575            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4576        );
 4577        assert_eq!(
 4578            editor.selections.display_ranges(cx),
 4579            vec![
 4580                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4581                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4582                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4583                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4584            ]
 4585        );
 4586    });
 4587}
 4588
 4589#[gpui::test]
 4590fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4591    init_test(cx, |_| {});
 4592
 4593    let editor = cx.add_window(|window, cx| {
 4594        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4595        build_editor(buffer, window, cx)
 4596    });
 4597    _ = editor.update(cx, |editor, window, cx| {
 4598        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4599        editor.insert_blocks(
 4600            [BlockProperties {
 4601                style: BlockStyle::Fixed,
 4602                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4603                height: Some(1),
 4604                render: Arc::new(|_| div().into_any()),
 4605                priority: 0,
 4606                render_in_minimap: true,
 4607            }],
 4608            Some(Autoscroll::fit()),
 4609            cx,
 4610        );
 4611        editor.change_selections(None, window, cx, |s| {
 4612            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4613        });
 4614        editor.move_line_down(&MoveLineDown, window, cx);
 4615    });
 4616}
 4617
 4618#[gpui::test]
 4619async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4620    init_test(cx, |_| {});
 4621
 4622    let mut cx = EditorTestContext::new(cx).await;
 4623    cx.set_state(
 4624        &"
 4625            ˇzero
 4626            one
 4627            two
 4628            three
 4629            four
 4630            five
 4631        "
 4632        .unindent(),
 4633    );
 4634
 4635    // Create a four-line block that replaces three lines of text.
 4636    cx.update_editor(|editor, window, cx| {
 4637        let snapshot = editor.snapshot(window, cx);
 4638        let snapshot = &snapshot.buffer_snapshot;
 4639        let placement = BlockPlacement::Replace(
 4640            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4641        );
 4642        editor.insert_blocks(
 4643            [BlockProperties {
 4644                placement,
 4645                height: Some(4),
 4646                style: BlockStyle::Sticky,
 4647                render: Arc::new(|_| gpui::div().into_any_element()),
 4648                priority: 0,
 4649                render_in_minimap: true,
 4650            }],
 4651            None,
 4652            cx,
 4653        );
 4654    });
 4655
 4656    // Move down so that the cursor touches the block.
 4657    cx.update_editor(|editor, window, cx| {
 4658        editor.move_down(&Default::default(), window, cx);
 4659    });
 4660    cx.assert_editor_state(
 4661        &"
 4662            zero
 4663            «one
 4664            two
 4665            threeˇ»
 4666            four
 4667            five
 4668        "
 4669        .unindent(),
 4670    );
 4671
 4672    // Move down past the block.
 4673    cx.update_editor(|editor, window, cx| {
 4674        editor.move_down(&Default::default(), window, cx);
 4675    });
 4676    cx.assert_editor_state(
 4677        &"
 4678            zero
 4679            one
 4680            two
 4681            three
 4682            ˇfour
 4683            five
 4684        "
 4685        .unindent(),
 4686    );
 4687}
 4688
 4689#[gpui::test]
 4690fn test_transpose(cx: &mut TestAppContext) {
 4691    init_test(cx, |_| {});
 4692
 4693    _ = cx.add_window(|window, cx| {
 4694        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4695        editor.set_style(EditorStyle::default(), window, cx);
 4696        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4697        editor.transpose(&Default::default(), window, cx);
 4698        assert_eq!(editor.text(cx), "bac");
 4699        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4700
 4701        editor.transpose(&Default::default(), window, cx);
 4702        assert_eq!(editor.text(cx), "bca");
 4703        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4704
 4705        editor.transpose(&Default::default(), window, cx);
 4706        assert_eq!(editor.text(cx), "bac");
 4707        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4708
 4709        editor
 4710    });
 4711
 4712    _ = cx.add_window(|window, cx| {
 4713        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4714        editor.set_style(EditorStyle::default(), window, cx);
 4715        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4716        editor.transpose(&Default::default(), window, cx);
 4717        assert_eq!(editor.text(cx), "acb\nde");
 4718        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4719
 4720        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4721        editor.transpose(&Default::default(), window, cx);
 4722        assert_eq!(editor.text(cx), "acbd\ne");
 4723        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4724
 4725        editor.transpose(&Default::default(), window, cx);
 4726        assert_eq!(editor.text(cx), "acbde\n");
 4727        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4728
 4729        editor.transpose(&Default::default(), window, cx);
 4730        assert_eq!(editor.text(cx), "acbd\ne");
 4731        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4732
 4733        editor
 4734    });
 4735
 4736    _ = cx.add_window(|window, cx| {
 4737        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4738        editor.set_style(EditorStyle::default(), window, cx);
 4739        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4740        editor.transpose(&Default::default(), window, cx);
 4741        assert_eq!(editor.text(cx), "bacd\ne");
 4742        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4743
 4744        editor.transpose(&Default::default(), window, cx);
 4745        assert_eq!(editor.text(cx), "bcade\n");
 4746        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4747
 4748        editor.transpose(&Default::default(), window, cx);
 4749        assert_eq!(editor.text(cx), "bcda\ne");
 4750        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4751
 4752        editor.transpose(&Default::default(), window, cx);
 4753        assert_eq!(editor.text(cx), "bcade\n");
 4754        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4755
 4756        editor.transpose(&Default::default(), window, cx);
 4757        assert_eq!(editor.text(cx), "bcaed\n");
 4758        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4759
 4760        editor
 4761    });
 4762
 4763    _ = cx.add_window(|window, cx| {
 4764        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4765        editor.set_style(EditorStyle::default(), window, cx);
 4766        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4767        editor.transpose(&Default::default(), window, cx);
 4768        assert_eq!(editor.text(cx), "🏀🍐✋");
 4769        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4770
 4771        editor.transpose(&Default::default(), window, cx);
 4772        assert_eq!(editor.text(cx), "🏀✋🍐");
 4773        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4774
 4775        editor.transpose(&Default::default(), window, cx);
 4776        assert_eq!(editor.text(cx), "🏀🍐✋");
 4777        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4778
 4779        editor
 4780    });
 4781}
 4782
 4783#[gpui::test]
 4784async fn test_rewrap(cx: &mut TestAppContext) {
 4785    init_test(cx, |settings| {
 4786        settings.languages.extend([
 4787            (
 4788                "Markdown".into(),
 4789                LanguageSettingsContent {
 4790                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4791                    ..Default::default()
 4792                },
 4793            ),
 4794            (
 4795                "Plain Text".into(),
 4796                LanguageSettingsContent {
 4797                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4798                    ..Default::default()
 4799                },
 4800            ),
 4801        ])
 4802    });
 4803
 4804    let mut cx = EditorTestContext::new(cx).await;
 4805
 4806    let language_with_c_comments = Arc::new(Language::new(
 4807        LanguageConfig {
 4808            line_comments: vec!["// ".into()],
 4809            ..LanguageConfig::default()
 4810        },
 4811        None,
 4812    ));
 4813    let language_with_pound_comments = Arc::new(Language::new(
 4814        LanguageConfig {
 4815            line_comments: vec!["# ".into()],
 4816            ..LanguageConfig::default()
 4817        },
 4818        None,
 4819    ));
 4820    let markdown_language = Arc::new(Language::new(
 4821        LanguageConfig {
 4822            name: "Markdown".into(),
 4823            ..LanguageConfig::default()
 4824        },
 4825        None,
 4826    ));
 4827    let language_with_doc_comments = Arc::new(Language::new(
 4828        LanguageConfig {
 4829            line_comments: vec!["// ".into(), "/// ".into()],
 4830            ..LanguageConfig::default()
 4831        },
 4832        Some(tree_sitter_rust::LANGUAGE.into()),
 4833    ));
 4834
 4835    let plaintext_language = Arc::new(Language::new(
 4836        LanguageConfig {
 4837            name: "Plain Text".into(),
 4838            ..LanguageConfig::default()
 4839        },
 4840        None,
 4841    ));
 4842
 4843    assert_rewrap(
 4844        indoc! {"
 4845            // ˇ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.
 4846        "},
 4847        indoc! {"
 4848            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4849            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4850            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4851            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4852            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4853            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4854            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4855            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4856            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4857            // porttitor id. Aliquam id accumsan eros.
 4858        "},
 4859        language_with_c_comments.clone(),
 4860        &mut cx,
 4861    );
 4862
 4863    // Test that rewrapping works inside of a selection
 4864    assert_rewrap(
 4865        indoc! {"
 4866            «// 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.ˇ»
 4867        "},
 4868        indoc! {"
 4869            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4870            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4871            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4872            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4873            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4874            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4875            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4876            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4877            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4878            // porttitor id. Aliquam id accumsan eros.ˇ»
 4879        "},
 4880        language_with_c_comments.clone(),
 4881        &mut cx,
 4882    );
 4883
 4884    // Test that cursors that expand to the same region are collapsed.
 4885    assert_rewrap(
 4886        indoc! {"
 4887            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4888            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4889            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4890            // ˇ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.
 4891        "},
 4892        indoc! {"
 4893            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4894            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4895            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4896            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4897            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4898            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4899            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4900            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4901            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4902            // porttitor id. Aliquam id accumsan eros.
 4903        "},
 4904        language_with_c_comments.clone(),
 4905        &mut cx,
 4906    );
 4907
 4908    // Test that non-contiguous selections are treated separately.
 4909    assert_rewrap(
 4910        indoc! {"
 4911            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4912            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4913            //
 4914            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4915            // ˇ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.
 4916        "},
 4917        indoc! {"
 4918            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4919            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4920            // auctor, eu lacinia sapien scelerisque.
 4921            //
 4922            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 4923            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4924            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 4925            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 4926            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 4927            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 4928            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 4929        "},
 4930        language_with_c_comments.clone(),
 4931        &mut cx,
 4932    );
 4933
 4934    // Test that different comment prefixes are supported.
 4935    assert_rewrap(
 4936        indoc! {"
 4937            # ˇ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.
 4938        "},
 4939        indoc! {"
 4940            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4941            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4942            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4943            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4944            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 4945            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 4946            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 4947            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 4948            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 4949            # accumsan eros.
 4950        "},
 4951        language_with_pound_comments.clone(),
 4952        &mut cx,
 4953    );
 4954
 4955    // Test that rewrapping is ignored outside of comments in most languages.
 4956    assert_rewrap(
 4957        indoc! {"
 4958            /// Adds two numbers.
 4959            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4960            fn add(a: u32, b: u32) -> u32 {
 4961                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ˇ
 4962            }
 4963        "},
 4964        indoc! {"
 4965            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 4966            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4967            fn add(a: u32, b: u32) -> u32 {
 4968                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ˇ
 4969            }
 4970        "},
 4971        language_with_doc_comments.clone(),
 4972        &mut cx,
 4973    );
 4974
 4975    // Test that rewrapping works in Markdown and Plain Text languages.
 4976    assert_rewrap(
 4977        indoc! {"
 4978            # Hello
 4979
 4980            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.
 4981        "},
 4982        indoc! {"
 4983            # Hello
 4984
 4985            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4986            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4987            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4988            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4989            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4990            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4991            Integer sit amet scelerisque nisi.
 4992        "},
 4993        markdown_language,
 4994        &mut cx,
 4995    );
 4996
 4997    assert_rewrap(
 4998        indoc! {"
 4999            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.
 5000        "},
 5001        indoc! {"
 5002            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5003            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5004            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5005            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5006            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5007            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5008            Integer sit amet scelerisque nisi.
 5009        "},
 5010        plaintext_language,
 5011        &mut cx,
 5012    );
 5013
 5014    // Test rewrapping unaligned comments in a selection.
 5015    assert_rewrap(
 5016        indoc! {"
 5017            fn foo() {
 5018                if true {
 5019            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5020            // Praesent semper egestas tellus id dignissim.ˇ»
 5021                    do_something();
 5022                } else {
 5023                    //
 5024                }
 5025            }
 5026        "},
 5027        indoc! {"
 5028            fn foo() {
 5029                if true {
 5030            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5031                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5032                    // egestas tellus id dignissim.ˇ»
 5033                    do_something();
 5034                } else {
 5035                    //
 5036                }
 5037            }
 5038        "},
 5039        language_with_doc_comments.clone(),
 5040        &mut cx,
 5041    );
 5042
 5043    assert_rewrap(
 5044        indoc! {"
 5045            fn foo() {
 5046                if true {
 5047            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5048            // Praesent semper egestas tellus id dignissim.»
 5049                    do_something();
 5050                } else {
 5051                    //
 5052                }
 5053
 5054            }
 5055        "},
 5056        indoc! {"
 5057            fn foo() {
 5058                if true {
 5059            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5060                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5061                    // egestas tellus id dignissim.»
 5062                    do_something();
 5063                } else {
 5064                    //
 5065                }
 5066
 5067            }
 5068        "},
 5069        language_with_doc_comments.clone(),
 5070        &mut cx,
 5071    );
 5072
 5073    #[track_caller]
 5074    fn assert_rewrap(
 5075        unwrapped_text: &str,
 5076        wrapped_text: &str,
 5077        language: Arc<Language>,
 5078        cx: &mut EditorTestContext,
 5079    ) {
 5080        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5081        cx.set_state(unwrapped_text);
 5082        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5083        cx.assert_editor_state(wrapped_text);
 5084    }
 5085}
 5086
 5087#[gpui::test]
 5088async fn test_hard_wrap(cx: &mut TestAppContext) {
 5089    init_test(cx, |_| {});
 5090    let mut cx = EditorTestContext::new(cx).await;
 5091
 5092    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5093    cx.update_editor(|editor, _, cx| {
 5094        editor.set_hard_wrap(Some(14), cx);
 5095    });
 5096
 5097    cx.set_state(indoc!(
 5098        "
 5099        one two three ˇ
 5100        "
 5101    ));
 5102    cx.simulate_input("four");
 5103    cx.run_until_parked();
 5104
 5105    cx.assert_editor_state(indoc!(
 5106        "
 5107        one two three
 5108        fourˇ
 5109        "
 5110    ));
 5111
 5112    cx.update_editor(|editor, window, cx| {
 5113        editor.newline(&Default::default(), window, cx);
 5114    });
 5115    cx.run_until_parked();
 5116    cx.assert_editor_state(indoc!(
 5117        "
 5118        one two three
 5119        four
 5120        ˇ
 5121        "
 5122    ));
 5123
 5124    cx.simulate_input("five");
 5125    cx.run_until_parked();
 5126    cx.assert_editor_state(indoc!(
 5127        "
 5128        one two three
 5129        four
 5130        fiveˇ
 5131        "
 5132    ));
 5133
 5134    cx.update_editor(|editor, window, cx| {
 5135        editor.newline(&Default::default(), window, cx);
 5136    });
 5137    cx.run_until_parked();
 5138    cx.simulate_input("# ");
 5139    cx.run_until_parked();
 5140    cx.assert_editor_state(indoc!(
 5141        "
 5142        one two three
 5143        four
 5144        five
 5145        # ˇ
 5146        "
 5147    ));
 5148
 5149    cx.update_editor(|editor, window, cx| {
 5150        editor.newline(&Default::default(), window, cx);
 5151    });
 5152    cx.run_until_parked();
 5153    cx.assert_editor_state(indoc!(
 5154        "
 5155        one two three
 5156        four
 5157        five
 5158        #\x20
 5159 5160        "
 5161    ));
 5162
 5163    cx.simulate_input(" 6");
 5164    cx.run_until_parked();
 5165    cx.assert_editor_state(indoc!(
 5166        "
 5167        one two three
 5168        four
 5169        five
 5170        #
 5171        # 6ˇ
 5172        "
 5173    ));
 5174}
 5175
 5176#[gpui::test]
 5177async fn test_clipboard(cx: &mut TestAppContext) {
 5178    init_test(cx, |_| {});
 5179
 5180    let mut cx = EditorTestContext::new(cx).await;
 5181
 5182    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5183    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5184    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5185
 5186    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5187    cx.set_state("two ˇfour ˇsix ˇ");
 5188    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5189    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5190
 5191    // Paste again but with only two cursors. Since the number of cursors doesn't
 5192    // match the number of slices in the clipboard, the entire clipboard text
 5193    // is pasted at each cursor.
 5194    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5195    cx.update_editor(|e, window, cx| {
 5196        e.handle_input("( ", window, cx);
 5197        e.paste(&Paste, window, cx);
 5198        e.handle_input(") ", window, cx);
 5199    });
 5200    cx.assert_editor_state(
 5201        &([
 5202            "( one✅ ",
 5203            "three ",
 5204            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5205            "three ",
 5206            "five ) ˇ",
 5207        ]
 5208        .join("\n")),
 5209    );
 5210
 5211    // Cut with three selections, one of which is full-line.
 5212    cx.set_state(indoc! {"
 5213        1«2ˇ»3
 5214        4ˇ567
 5215        «8ˇ»9"});
 5216    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5217    cx.assert_editor_state(indoc! {"
 5218        1ˇ3
 5219        ˇ9"});
 5220
 5221    // Paste with three selections, noticing how the copied selection that was full-line
 5222    // gets inserted before the second cursor.
 5223    cx.set_state(indoc! {"
 5224        1ˇ3
 5225 5226        «oˇ»ne"});
 5227    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5228    cx.assert_editor_state(indoc! {"
 5229        12ˇ3
 5230        4567
 5231 5232        8ˇne"});
 5233
 5234    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5235    cx.set_state(indoc! {"
 5236        The quick brown
 5237        fox juˇmps over
 5238        the lazy dog"});
 5239    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5240    assert_eq!(
 5241        cx.read_from_clipboard()
 5242            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5243        Some("fox jumps over\n".to_string())
 5244    );
 5245
 5246    // Paste with three selections, noticing how the copied full-line selection is inserted
 5247    // before the empty selections but replaces the selection that is non-empty.
 5248    cx.set_state(indoc! {"
 5249        Tˇhe quick brown
 5250        «foˇ»x jumps over
 5251        tˇhe lazy dog"});
 5252    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5253    cx.assert_editor_state(indoc! {"
 5254        fox jumps over
 5255        Tˇhe quick brown
 5256        fox jumps over
 5257        ˇx jumps over
 5258        fox jumps over
 5259        tˇhe lazy dog"});
 5260}
 5261
 5262#[gpui::test]
 5263async fn test_copy_trim(cx: &mut TestAppContext) {
 5264    init_test(cx, |_| {});
 5265
 5266    let mut cx = EditorTestContext::new(cx).await;
 5267    cx.set_state(
 5268        r#"            «for selection in selections.iter() {
 5269            let mut start = selection.start;
 5270            let mut end = selection.end;
 5271            let is_entire_line = selection.is_empty();
 5272            if is_entire_line {
 5273                start = Point::new(start.row, 0);ˇ»
 5274                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5275            }
 5276        "#,
 5277    );
 5278    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5279    assert_eq!(
 5280        cx.read_from_clipboard()
 5281            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5282        Some(
 5283            "for selection in selections.iter() {
 5284            let mut start = selection.start;
 5285            let mut end = selection.end;
 5286            let is_entire_line = selection.is_empty();
 5287            if is_entire_line {
 5288                start = Point::new(start.row, 0);"
 5289                .to_string()
 5290        ),
 5291        "Regular copying preserves all indentation selected",
 5292    );
 5293    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5294    assert_eq!(
 5295        cx.read_from_clipboard()
 5296            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5297        Some(
 5298            "for selection in selections.iter() {
 5299let mut start = selection.start;
 5300let mut end = selection.end;
 5301let is_entire_line = selection.is_empty();
 5302if is_entire_line {
 5303    start = Point::new(start.row, 0);"
 5304                .to_string()
 5305        ),
 5306        "Copying with stripping should strip all leading whitespaces"
 5307    );
 5308
 5309    cx.set_state(
 5310        r#"       «     for selection in selections.iter() {
 5311            let mut start = selection.start;
 5312            let mut end = selection.end;
 5313            let is_entire_line = selection.is_empty();
 5314            if is_entire_line {
 5315                start = Point::new(start.row, 0);ˇ»
 5316                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5317            }
 5318        "#,
 5319    );
 5320    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5321    assert_eq!(
 5322        cx.read_from_clipboard()
 5323            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5324        Some(
 5325            "     for selection in selections.iter() {
 5326            let mut start = selection.start;
 5327            let mut end = selection.end;
 5328            let is_entire_line = selection.is_empty();
 5329            if is_entire_line {
 5330                start = Point::new(start.row, 0);"
 5331                .to_string()
 5332        ),
 5333        "Regular copying preserves all indentation selected",
 5334    );
 5335    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5336    assert_eq!(
 5337        cx.read_from_clipboard()
 5338            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5339        Some(
 5340            "for selection in selections.iter() {
 5341let mut start = selection.start;
 5342let mut end = selection.end;
 5343let is_entire_line = selection.is_empty();
 5344if is_entire_line {
 5345    start = Point::new(start.row, 0);"
 5346                .to_string()
 5347        ),
 5348        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5349    );
 5350
 5351    cx.set_state(
 5352        r#"       «ˇ     for selection in selections.iter() {
 5353            let mut start = selection.start;
 5354            let mut end = selection.end;
 5355            let is_entire_line = selection.is_empty();
 5356            if is_entire_line {
 5357                start = Point::new(start.row, 0);»
 5358                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5359            }
 5360        "#,
 5361    );
 5362    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5363    assert_eq!(
 5364        cx.read_from_clipboard()
 5365            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5366        Some(
 5367            "     for selection in selections.iter() {
 5368            let mut start = selection.start;
 5369            let mut end = selection.end;
 5370            let is_entire_line = selection.is_empty();
 5371            if is_entire_line {
 5372                start = Point::new(start.row, 0);"
 5373                .to_string()
 5374        ),
 5375        "Regular copying for reverse selection works the same",
 5376    );
 5377    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5378    assert_eq!(
 5379        cx.read_from_clipboard()
 5380            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5381        Some(
 5382            "for selection in selections.iter() {
 5383let mut start = selection.start;
 5384let mut end = selection.end;
 5385let is_entire_line = selection.is_empty();
 5386if is_entire_line {
 5387    start = Point::new(start.row, 0);"
 5388                .to_string()
 5389        ),
 5390        "Copying with stripping for reverse selection works the same"
 5391    );
 5392
 5393    cx.set_state(
 5394        r#"            for selection «in selections.iter() {
 5395            let mut start = selection.start;
 5396            let mut end = selection.end;
 5397            let is_entire_line = selection.is_empty();
 5398            if is_entire_line {
 5399                start = Point::new(start.row, 0);ˇ»
 5400                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5401            }
 5402        "#,
 5403    );
 5404    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5405    assert_eq!(
 5406        cx.read_from_clipboard()
 5407            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5408        Some(
 5409            "in selections.iter() {
 5410            let mut start = selection.start;
 5411            let mut end = selection.end;
 5412            let is_entire_line = selection.is_empty();
 5413            if is_entire_line {
 5414                start = Point::new(start.row, 0);"
 5415                .to_string()
 5416        ),
 5417        "When selecting past the indent, the copying works as usual",
 5418    );
 5419    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5420    assert_eq!(
 5421        cx.read_from_clipboard()
 5422            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5423        Some(
 5424            "in selections.iter() {
 5425            let mut start = selection.start;
 5426            let mut end = selection.end;
 5427            let is_entire_line = selection.is_empty();
 5428            if is_entire_line {
 5429                start = Point::new(start.row, 0);"
 5430                .to_string()
 5431        ),
 5432        "When selecting past the indent, nothing is trimmed"
 5433    );
 5434
 5435    cx.set_state(
 5436        r#"            «for selection in selections.iter() {
 5437            let mut start = selection.start;
 5438
 5439            let mut end = selection.end;
 5440            let is_entire_line = selection.is_empty();
 5441            if is_entire_line {
 5442                start = Point::new(start.row, 0);
 5443ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5444            }
 5445        "#,
 5446    );
 5447    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5448    assert_eq!(
 5449        cx.read_from_clipboard()
 5450            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5451        Some(
 5452            "for selection in selections.iter() {
 5453let mut start = selection.start;
 5454
 5455let mut end = selection.end;
 5456let is_entire_line = selection.is_empty();
 5457if is_entire_line {
 5458    start = Point::new(start.row, 0);
 5459"
 5460            .to_string()
 5461        ),
 5462        "Copying with stripping should ignore empty lines"
 5463    );
 5464}
 5465
 5466#[gpui::test]
 5467async fn test_paste_multiline(cx: &mut TestAppContext) {
 5468    init_test(cx, |_| {});
 5469
 5470    let mut cx = EditorTestContext::new(cx).await;
 5471    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5472
 5473    // Cut an indented block, without the leading whitespace.
 5474    cx.set_state(indoc! {"
 5475        const a: B = (
 5476            c(),
 5477            «d(
 5478                e,
 5479                f
 5480            )ˇ»
 5481        );
 5482    "});
 5483    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5484    cx.assert_editor_state(indoc! {"
 5485        const a: B = (
 5486            c(),
 5487            ˇ
 5488        );
 5489    "});
 5490
 5491    // Paste it at the same position.
 5492    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        const a: B = (
 5495            c(),
 5496            d(
 5497                e,
 5498                f
 5499 5500        );
 5501    "});
 5502
 5503    // Paste it at a line with a lower indent level.
 5504    cx.set_state(indoc! {"
 5505        ˇ
 5506        const a: B = (
 5507            c(),
 5508        );
 5509    "});
 5510    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5511    cx.assert_editor_state(indoc! {"
 5512        d(
 5513            e,
 5514            f
 5515 5516        const a: B = (
 5517            c(),
 5518        );
 5519    "});
 5520
 5521    // Cut an indented block, with the leading whitespace.
 5522    cx.set_state(indoc! {"
 5523        const a: B = (
 5524            c(),
 5525        «    d(
 5526                e,
 5527                f
 5528            )
 5529        ˇ»);
 5530    "});
 5531    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5532    cx.assert_editor_state(indoc! {"
 5533        const a: B = (
 5534            c(),
 5535        ˇ);
 5536    "});
 5537
 5538    // Paste it at the same position.
 5539    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5540    cx.assert_editor_state(indoc! {"
 5541        const a: B = (
 5542            c(),
 5543            d(
 5544                e,
 5545                f
 5546            )
 5547        ˇ);
 5548    "});
 5549
 5550    // Paste it at a line with a higher indent level.
 5551    cx.set_state(indoc! {"
 5552        const a: B = (
 5553            c(),
 5554            d(
 5555                e,
 5556 5557            )
 5558        );
 5559    "});
 5560    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5561    cx.assert_editor_state(indoc! {"
 5562        const a: B = (
 5563            c(),
 5564            d(
 5565                e,
 5566                f    d(
 5567                    e,
 5568                    f
 5569                )
 5570        ˇ
 5571            )
 5572        );
 5573    "});
 5574
 5575    // Copy an indented block, starting mid-line
 5576    cx.set_state(indoc! {"
 5577        const a: B = (
 5578            c(),
 5579            somethin«g(
 5580                e,
 5581                f
 5582            )ˇ»
 5583        );
 5584    "});
 5585    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5586
 5587    // Paste it on a line with a lower indent level
 5588    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5589    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        const a: B = (
 5592            c(),
 5593            something(
 5594                e,
 5595                f
 5596            )
 5597        );
 5598        g(
 5599            e,
 5600            f
 5601"});
 5602}
 5603
 5604#[gpui::test]
 5605async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5606    init_test(cx, |_| {});
 5607
 5608    cx.write_to_clipboard(ClipboardItem::new_string(
 5609        "    d(\n        e\n    );\n".into(),
 5610    ));
 5611
 5612    let mut cx = EditorTestContext::new(cx).await;
 5613    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5614
 5615    cx.set_state(indoc! {"
 5616        fn a() {
 5617            b();
 5618            if c() {
 5619                ˇ
 5620            }
 5621        }
 5622    "});
 5623
 5624    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5625    cx.assert_editor_state(indoc! {"
 5626        fn a() {
 5627            b();
 5628            if c() {
 5629                d(
 5630                    e
 5631                );
 5632        ˇ
 5633            }
 5634        }
 5635    "});
 5636
 5637    cx.set_state(indoc! {"
 5638        fn a() {
 5639            b();
 5640            ˇ
 5641        }
 5642    "});
 5643
 5644    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5645    cx.assert_editor_state(indoc! {"
 5646        fn a() {
 5647            b();
 5648            d(
 5649                e
 5650            );
 5651        ˇ
 5652        }
 5653    "});
 5654}
 5655
 5656#[gpui::test]
 5657fn test_select_all(cx: &mut TestAppContext) {
 5658    init_test(cx, |_| {});
 5659
 5660    let editor = cx.add_window(|window, cx| {
 5661        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5662        build_editor(buffer, window, cx)
 5663    });
 5664    _ = editor.update(cx, |editor, window, cx| {
 5665        editor.select_all(&SelectAll, window, cx);
 5666        assert_eq!(
 5667            editor.selections.display_ranges(cx),
 5668            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5669        );
 5670    });
 5671}
 5672
 5673#[gpui::test]
 5674fn test_select_line(cx: &mut TestAppContext) {
 5675    init_test(cx, |_| {});
 5676
 5677    let editor = cx.add_window(|window, cx| {
 5678        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5679        build_editor(buffer, window, cx)
 5680    });
 5681    _ = editor.update(cx, |editor, window, cx| {
 5682        editor.change_selections(None, window, cx, |s| {
 5683            s.select_display_ranges([
 5684                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5685                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5686                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5687                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5688            ])
 5689        });
 5690        editor.select_line(&SelectLine, window, cx);
 5691        assert_eq!(
 5692            editor.selections.display_ranges(cx),
 5693            vec![
 5694                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5695                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5696            ]
 5697        );
 5698    });
 5699
 5700    _ = editor.update(cx, |editor, window, cx| {
 5701        editor.select_line(&SelectLine, window, cx);
 5702        assert_eq!(
 5703            editor.selections.display_ranges(cx),
 5704            vec![
 5705                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5706                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5707            ]
 5708        );
 5709    });
 5710
 5711    _ = editor.update(cx, |editor, window, cx| {
 5712        editor.select_line(&SelectLine, window, cx);
 5713        assert_eq!(
 5714            editor.selections.display_ranges(cx),
 5715            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5716        );
 5717    });
 5718}
 5719
 5720#[gpui::test]
 5721async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5722    init_test(cx, |_| {});
 5723    let mut cx = EditorTestContext::new(cx).await;
 5724
 5725    #[track_caller]
 5726    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5727        cx.set_state(initial_state);
 5728        cx.update_editor(|e, window, cx| {
 5729            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5730        });
 5731        cx.assert_editor_state(expected_state);
 5732    }
 5733
 5734    // Selection starts and ends at the middle of lines, left-to-right
 5735    test(
 5736        &mut cx,
 5737        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5738        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5739    );
 5740    // Same thing, right-to-left
 5741    test(
 5742        &mut cx,
 5743        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5744        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5745    );
 5746
 5747    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5748    test(
 5749        &mut cx,
 5750        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5751        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5752    );
 5753    // Same thing, right-to-left
 5754    test(
 5755        &mut cx,
 5756        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5757        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5758    );
 5759
 5760    // Whole buffer, left-to-right, last line ends with newline
 5761    test(
 5762        &mut cx,
 5763        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5764        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5765    );
 5766    // Same thing, right-to-left
 5767    test(
 5768        &mut cx,
 5769        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5770        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5771    );
 5772
 5773    // Starts at the end of a line, ends at the start of another
 5774    test(
 5775        &mut cx,
 5776        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5777        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5778    );
 5779}
 5780
 5781#[gpui::test]
 5782async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5783    init_test(cx, |_| {});
 5784
 5785    let editor = cx.add_window(|window, cx| {
 5786        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5787        build_editor(buffer, window, cx)
 5788    });
 5789
 5790    // setup
 5791    _ = editor.update(cx, |editor, window, cx| {
 5792        editor.fold_creases(
 5793            vec![
 5794                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5795                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5796                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5797            ],
 5798            true,
 5799            window,
 5800            cx,
 5801        );
 5802        assert_eq!(
 5803            editor.display_text(cx),
 5804            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5805        );
 5806    });
 5807
 5808    _ = editor.update(cx, |editor, window, cx| {
 5809        editor.change_selections(None, window, cx, |s| {
 5810            s.select_display_ranges([
 5811                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5812                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5813                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5814                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5815            ])
 5816        });
 5817        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5818        assert_eq!(
 5819            editor.display_text(cx),
 5820            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5821        );
 5822    });
 5823    EditorTestContext::for_editor(editor, cx)
 5824        .await
 5825        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5826
 5827    _ = editor.update(cx, |editor, window, cx| {
 5828        editor.change_selections(None, window, cx, |s| {
 5829            s.select_display_ranges([
 5830                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5831            ])
 5832        });
 5833        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5834        assert_eq!(
 5835            editor.display_text(cx),
 5836            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5837        );
 5838        assert_eq!(
 5839            editor.selections.display_ranges(cx),
 5840            [
 5841                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5842                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5843                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5844                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5845                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5846                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5847                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5848            ]
 5849        );
 5850    });
 5851    EditorTestContext::for_editor(editor, cx)
 5852        .await
 5853        .assert_editor_state(
 5854            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5855        );
 5856}
 5857
 5858#[gpui::test]
 5859async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5860    init_test(cx, |_| {});
 5861
 5862    let mut cx = EditorTestContext::new(cx).await;
 5863
 5864    cx.set_state(indoc!(
 5865        r#"abc
 5866           defˇghi
 5867
 5868           jk
 5869           nlmo
 5870           "#
 5871    ));
 5872
 5873    cx.update_editor(|editor, window, cx| {
 5874        editor.add_selection_above(&Default::default(), window, cx);
 5875    });
 5876
 5877    cx.assert_editor_state(indoc!(
 5878        r#"abcˇ
 5879           defˇghi
 5880
 5881           jk
 5882           nlmo
 5883           "#
 5884    ));
 5885
 5886    cx.update_editor(|editor, window, cx| {
 5887        editor.add_selection_above(&Default::default(), window, cx);
 5888    });
 5889
 5890    cx.assert_editor_state(indoc!(
 5891        r#"abcˇ
 5892            defˇghi
 5893
 5894            jk
 5895            nlmo
 5896            "#
 5897    ));
 5898
 5899    cx.update_editor(|editor, window, cx| {
 5900        editor.add_selection_below(&Default::default(), window, cx);
 5901    });
 5902
 5903    cx.assert_editor_state(indoc!(
 5904        r#"abc
 5905           defˇghi
 5906
 5907           jk
 5908           nlmo
 5909           "#
 5910    ));
 5911
 5912    cx.update_editor(|editor, window, cx| {
 5913        editor.undo_selection(&Default::default(), window, cx);
 5914    });
 5915
 5916    cx.assert_editor_state(indoc!(
 5917        r#"abcˇ
 5918           defˇghi
 5919
 5920           jk
 5921           nlmo
 5922           "#
 5923    ));
 5924
 5925    cx.update_editor(|editor, window, cx| {
 5926        editor.redo_selection(&Default::default(), window, cx);
 5927    });
 5928
 5929    cx.assert_editor_state(indoc!(
 5930        r#"abc
 5931           defˇghi
 5932
 5933           jk
 5934           nlmo
 5935           "#
 5936    ));
 5937
 5938    cx.update_editor(|editor, window, cx| {
 5939        editor.add_selection_below(&Default::default(), window, cx);
 5940    });
 5941
 5942    cx.assert_editor_state(indoc!(
 5943        r#"abc
 5944           defˇghi
 5945
 5946           jk
 5947           nlmˇo
 5948           "#
 5949    ));
 5950
 5951    cx.update_editor(|editor, window, cx| {
 5952        editor.add_selection_below(&Default::default(), window, cx);
 5953    });
 5954
 5955    cx.assert_editor_state(indoc!(
 5956        r#"abc
 5957           defˇghi
 5958
 5959           jk
 5960           nlmˇo
 5961           "#
 5962    ));
 5963
 5964    // change selections
 5965    cx.set_state(indoc!(
 5966        r#"abc
 5967           def«ˇg»hi
 5968
 5969           jk
 5970           nlmo
 5971           "#
 5972    ));
 5973
 5974    cx.update_editor(|editor, window, cx| {
 5975        editor.add_selection_below(&Default::default(), window, cx);
 5976    });
 5977
 5978    cx.assert_editor_state(indoc!(
 5979        r#"abc
 5980           def«ˇg»hi
 5981
 5982           jk
 5983           nlm«ˇo»
 5984           "#
 5985    ));
 5986
 5987    cx.update_editor(|editor, window, cx| {
 5988        editor.add_selection_below(&Default::default(), window, cx);
 5989    });
 5990
 5991    cx.assert_editor_state(indoc!(
 5992        r#"abc
 5993           def«ˇg»hi
 5994
 5995           jk
 5996           nlm«ˇo»
 5997           "#
 5998    ));
 5999
 6000    cx.update_editor(|editor, window, cx| {
 6001        editor.add_selection_above(&Default::default(), window, cx);
 6002    });
 6003
 6004    cx.assert_editor_state(indoc!(
 6005        r#"abc
 6006           def«ˇg»hi
 6007
 6008           jk
 6009           nlmo
 6010           "#
 6011    ));
 6012
 6013    cx.update_editor(|editor, window, cx| {
 6014        editor.add_selection_above(&Default::default(), window, cx);
 6015    });
 6016
 6017    cx.assert_editor_state(indoc!(
 6018        r#"abc
 6019           def«ˇg»hi
 6020
 6021           jk
 6022           nlmo
 6023           "#
 6024    ));
 6025
 6026    // Change selections again
 6027    cx.set_state(indoc!(
 6028        r#"a«bc
 6029           defgˇ»hi
 6030
 6031           jk
 6032           nlmo
 6033           "#
 6034    ));
 6035
 6036    cx.update_editor(|editor, window, cx| {
 6037        editor.add_selection_below(&Default::default(), window, cx);
 6038    });
 6039
 6040    cx.assert_editor_state(indoc!(
 6041        r#"a«bcˇ»
 6042           d«efgˇ»hi
 6043
 6044           j«kˇ»
 6045           nlmo
 6046           "#
 6047    ));
 6048
 6049    cx.update_editor(|editor, window, cx| {
 6050        editor.add_selection_below(&Default::default(), window, cx);
 6051    });
 6052    cx.assert_editor_state(indoc!(
 6053        r#"a«bcˇ»
 6054           d«efgˇ»hi
 6055
 6056           j«kˇ»
 6057           n«lmoˇ»
 6058           "#
 6059    ));
 6060    cx.update_editor(|editor, window, cx| {
 6061        editor.add_selection_above(&Default::default(), window, cx);
 6062    });
 6063
 6064    cx.assert_editor_state(indoc!(
 6065        r#"a«bcˇ»
 6066           d«efgˇ»hi
 6067
 6068           j«kˇ»
 6069           nlmo
 6070           "#
 6071    ));
 6072
 6073    // Change selections again
 6074    cx.set_state(indoc!(
 6075        r#"abc
 6076           d«ˇefghi
 6077
 6078           jk
 6079           nlm»o
 6080           "#
 6081    ));
 6082
 6083    cx.update_editor(|editor, window, cx| {
 6084        editor.add_selection_above(&Default::default(), window, cx);
 6085    });
 6086
 6087    cx.assert_editor_state(indoc!(
 6088        r#"a«ˇbc»
 6089           d«ˇef»ghi
 6090
 6091           j«ˇk»
 6092           n«ˇlm»o
 6093           "#
 6094    ));
 6095
 6096    cx.update_editor(|editor, window, cx| {
 6097        editor.add_selection_below(&Default::default(), window, cx);
 6098    });
 6099
 6100    cx.assert_editor_state(indoc!(
 6101        r#"abc
 6102           d«ˇef»ghi
 6103
 6104           j«ˇk»
 6105           n«ˇlm»o
 6106           "#
 6107    ));
 6108}
 6109
 6110#[gpui::test]
 6111async fn test_select_next(cx: &mut TestAppContext) {
 6112    init_test(cx, |_| {});
 6113
 6114    let mut cx = EditorTestContext::new(cx).await;
 6115    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6116
 6117    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6118        .unwrap();
 6119    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6120
 6121    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6122        .unwrap();
 6123    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6124
 6125    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6126    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6127
 6128    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6129    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6130
 6131    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6132        .unwrap();
 6133    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6134
 6135    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6136        .unwrap();
 6137    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6138
 6139    // Test selection direction should be preserved
 6140    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6141
 6142    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6143        .unwrap();
 6144    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6145}
 6146
 6147#[gpui::test]
 6148async fn test_select_all_matches(cx: &mut TestAppContext) {
 6149    init_test(cx, |_| {});
 6150
 6151    let mut cx = EditorTestContext::new(cx).await;
 6152
 6153    // Test caret-only selections
 6154    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6155    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6156        .unwrap();
 6157    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6158
 6159    // Test left-to-right selections
 6160    cx.set_state("abc\n«abcˇ»\nabc");
 6161    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6162        .unwrap();
 6163    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6164
 6165    // Test right-to-left selections
 6166    cx.set_state("abc\n«ˇabc»\nabc");
 6167    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6168        .unwrap();
 6169    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6170
 6171    // Test selecting whitespace with caret selection
 6172    cx.set_state("abc\nˇ   abc\nabc");
 6173    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6174        .unwrap();
 6175    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6176
 6177    // Test selecting whitespace with left-to-right selection
 6178    cx.set_state("abc\n«ˇ  »abc\nabc");
 6179    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6180        .unwrap();
 6181    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6182
 6183    // Test no matches with right-to-left selection
 6184    cx.set_state("abc\n«  ˇ»abc\nabc");
 6185    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6186        .unwrap();
 6187    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6188}
 6189
 6190#[gpui::test]
 6191async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6192    init_test(cx, |_| {});
 6193
 6194    let mut cx = EditorTestContext::new(cx).await;
 6195
 6196    let large_body_1 = "\nd".repeat(200);
 6197    let large_body_2 = "\ne".repeat(200);
 6198
 6199    cx.set_state(&format!(
 6200        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6201    ));
 6202    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6203        let scroll_position = editor.scroll_position(cx);
 6204        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6205        scroll_position
 6206    });
 6207
 6208    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6209        .unwrap();
 6210    cx.assert_editor_state(&format!(
 6211        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6212    ));
 6213    let scroll_position_after_selection =
 6214        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6215    assert_eq!(
 6216        initial_scroll_position, scroll_position_after_selection,
 6217        "Scroll position should not change after selecting all matches"
 6218    );
 6219}
 6220
 6221#[gpui::test]
 6222async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6223    init_test(cx, |_| {});
 6224
 6225    let mut cx = EditorLspTestContext::new_rust(
 6226        lsp::ServerCapabilities {
 6227            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6228            ..Default::default()
 6229        },
 6230        cx,
 6231    )
 6232    .await;
 6233
 6234    cx.set_state(indoc! {"
 6235        line 1
 6236        line 2
 6237        linˇe 3
 6238        line 4
 6239        line 5
 6240    "});
 6241
 6242    // Make an edit
 6243    cx.update_editor(|editor, window, cx| {
 6244        editor.handle_input("X", window, cx);
 6245    });
 6246
 6247    // Move cursor to a different position
 6248    cx.update_editor(|editor, window, cx| {
 6249        editor.change_selections(None, window, cx, |s| {
 6250            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6251        });
 6252    });
 6253
 6254    cx.assert_editor_state(indoc! {"
 6255        line 1
 6256        line 2
 6257        linXe 3
 6258        line 4
 6259        liˇne 5
 6260    "});
 6261
 6262    cx.lsp
 6263        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6264            Ok(Some(vec![lsp::TextEdit::new(
 6265                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6266                "PREFIX ".to_string(),
 6267            )]))
 6268        });
 6269
 6270    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6271        .unwrap()
 6272        .await
 6273        .unwrap();
 6274
 6275    cx.assert_editor_state(indoc! {"
 6276        PREFIX line 1
 6277        line 2
 6278        linXe 3
 6279        line 4
 6280        liˇne 5
 6281    "});
 6282
 6283    // Undo formatting
 6284    cx.update_editor(|editor, window, cx| {
 6285        editor.undo(&Default::default(), window, cx);
 6286    });
 6287
 6288    // Verify cursor moved back to position after edit
 6289    cx.assert_editor_state(indoc! {"
 6290        line 1
 6291        line 2
 6292        linXˇe 3
 6293        line 4
 6294        line 5
 6295    "});
 6296}
 6297
 6298#[gpui::test]
 6299async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6300    init_test(cx, |_| {});
 6301
 6302    let mut cx = EditorTestContext::new(cx).await;
 6303    cx.set_state(
 6304        r#"let foo = 2;
 6305lˇet foo = 2;
 6306let fooˇ = 2;
 6307let foo = 2;
 6308let foo = ˇ2;"#,
 6309    );
 6310
 6311    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6312        .unwrap();
 6313    cx.assert_editor_state(
 6314        r#"let foo = 2;
 6315«letˇ» foo = 2;
 6316let «fooˇ» = 2;
 6317let foo = 2;
 6318let foo = «2ˇ»;"#,
 6319    );
 6320
 6321    // noop for multiple selections with different contents
 6322    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6323        .unwrap();
 6324    cx.assert_editor_state(
 6325        r#"let foo = 2;
 6326«letˇ» foo = 2;
 6327let «fooˇ» = 2;
 6328let foo = 2;
 6329let foo = «2ˇ»;"#,
 6330    );
 6331
 6332    // Test last selection direction should be preserved
 6333    cx.set_state(
 6334        r#"let foo = 2;
 6335let foo = 2;
 6336let «fooˇ» = 2;
 6337let «ˇfoo» = 2;
 6338let foo = 2;"#,
 6339    );
 6340
 6341    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6342        .unwrap();
 6343    cx.assert_editor_state(
 6344        r#"let foo = 2;
 6345let foo = 2;
 6346let «fooˇ» = 2;
 6347let «ˇfoo» = 2;
 6348let «ˇfoo» = 2;"#,
 6349    );
 6350}
 6351
 6352#[gpui::test]
 6353async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6354    init_test(cx, |_| {});
 6355
 6356    let mut cx =
 6357        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6358
 6359    cx.assert_editor_state(indoc! {"
 6360        ˇbbb
 6361        ccc
 6362
 6363        bbb
 6364        ccc
 6365        "});
 6366    cx.dispatch_action(SelectPrevious::default());
 6367    cx.assert_editor_state(indoc! {"
 6368                «bbbˇ»
 6369                ccc
 6370
 6371                bbb
 6372                ccc
 6373                "});
 6374    cx.dispatch_action(SelectPrevious::default());
 6375    cx.assert_editor_state(indoc! {"
 6376                «bbbˇ»
 6377                ccc
 6378
 6379                «bbbˇ»
 6380                ccc
 6381                "});
 6382}
 6383
 6384#[gpui::test]
 6385async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6386    init_test(cx, |_| {});
 6387
 6388    let mut cx = EditorTestContext::new(cx).await;
 6389    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6390
 6391    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6392        .unwrap();
 6393    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6394
 6395    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6396        .unwrap();
 6397    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6398
 6399    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6400    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6401
 6402    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6403    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6404
 6405    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6406        .unwrap();
 6407    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6408
 6409    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6410        .unwrap();
 6411    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6412}
 6413
 6414#[gpui::test]
 6415async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6416    init_test(cx, |_| {});
 6417
 6418    let mut cx = EditorTestContext::new(cx).await;
 6419    cx.set_state("");
 6420
 6421    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6422        .unwrap();
 6423    cx.assert_editor_state("«aˇ»");
 6424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6425        .unwrap();
 6426    cx.assert_editor_state("«aˇ»");
 6427}
 6428
 6429#[gpui::test]
 6430async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6431    init_test(cx, |_| {});
 6432
 6433    let mut cx = EditorTestContext::new(cx).await;
 6434    cx.set_state(
 6435        r#"let foo = 2;
 6436lˇet foo = 2;
 6437let fooˇ = 2;
 6438let foo = 2;
 6439let foo = ˇ2;"#,
 6440    );
 6441
 6442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6443        .unwrap();
 6444    cx.assert_editor_state(
 6445        r#"let foo = 2;
 6446«letˇ» foo = 2;
 6447let «fooˇ» = 2;
 6448let foo = 2;
 6449let foo = «2ˇ»;"#,
 6450    );
 6451
 6452    // noop for multiple selections with different contents
 6453    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6454        .unwrap();
 6455    cx.assert_editor_state(
 6456        r#"let foo = 2;
 6457«letˇ» foo = 2;
 6458let «fooˇ» = 2;
 6459let foo = 2;
 6460let foo = «2ˇ»;"#,
 6461    );
 6462}
 6463
 6464#[gpui::test]
 6465async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6466    init_test(cx, |_| {});
 6467
 6468    let mut cx = EditorTestContext::new(cx).await;
 6469    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6470
 6471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6472        .unwrap();
 6473    // selection direction is preserved
 6474    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6475
 6476    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6477        .unwrap();
 6478    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6479
 6480    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6481    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6482
 6483    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6484    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6485
 6486    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6487        .unwrap();
 6488    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6489
 6490    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6491        .unwrap();
 6492    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6493}
 6494
 6495#[gpui::test]
 6496async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6497    init_test(cx, |_| {});
 6498
 6499    let language = Arc::new(Language::new(
 6500        LanguageConfig::default(),
 6501        Some(tree_sitter_rust::LANGUAGE.into()),
 6502    ));
 6503
 6504    let text = r#"
 6505        use mod1::mod2::{mod3, mod4};
 6506
 6507        fn fn_1(param1: bool, param2: &str) {
 6508            let var1 = "text";
 6509        }
 6510    "#
 6511    .unindent();
 6512
 6513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6516
 6517    editor
 6518        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6519        .await;
 6520
 6521    editor.update_in(cx, |editor, window, cx| {
 6522        editor.change_selections(None, window, cx, |s| {
 6523            s.select_display_ranges([
 6524                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6525                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6526                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6527            ]);
 6528        });
 6529        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6530    });
 6531    editor.update(cx, |editor, cx| {
 6532        assert_text_with_selections(
 6533            editor,
 6534            indoc! {r#"
 6535                use mod1::mod2::{mod3, «mod4ˇ»};
 6536
 6537                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6538                    let var1 = "«ˇtext»";
 6539                }
 6540            "#},
 6541            cx,
 6542        );
 6543    });
 6544
 6545    editor.update_in(cx, |editor, window, cx| {
 6546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6547    });
 6548    editor.update(cx, |editor, cx| {
 6549        assert_text_with_selections(
 6550            editor,
 6551            indoc! {r#"
 6552                use mod1::mod2::«{mod3, mod4}ˇ»;
 6553
 6554                «ˇfn fn_1(param1: bool, param2: &str) {
 6555                    let var1 = "text";
 6556 6557            "#},
 6558            cx,
 6559        );
 6560    });
 6561
 6562    editor.update_in(cx, |editor, window, cx| {
 6563        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6564    });
 6565    assert_eq!(
 6566        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6567        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6568    );
 6569
 6570    // Trying to expand the selected syntax node one more time has no effect.
 6571    editor.update_in(cx, |editor, window, cx| {
 6572        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6573    });
 6574    assert_eq!(
 6575        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6576        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6577    );
 6578
 6579    editor.update_in(cx, |editor, window, cx| {
 6580        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6581    });
 6582    editor.update(cx, |editor, cx| {
 6583        assert_text_with_selections(
 6584            editor,
 6585            indoc! {r#"
 6586                use mod1::mod2::«{mod3, mod4}ˇ»;
 6587
 6588                «ˇfn fn_1(param1: bool, param2: &str) {
 6589                    let var1 = "text";
 6590 6591            "#},
 6592            cx,
 6593        );
 6594    });
 6595
 6596    editor.update_in(cx, |editor, window, cx| {
 6597        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6598    });
 6599    editor.update(cx, |editor, cx| {
 6600        assert_text_with_selections(
 6601            editor,
 6602            indoc! {r#"
 6603                use mod1::mod2::{mod3, «mod4ˇ»};
 6604
 6605                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6606                    let var1 = "«ˇtext»";
 6607                }
 6608            "#},
 6609            cx,
 6610        );
 6611    });
 6612
 6613    editor.update_in(cx, |editor, window, cx| {
 6614        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6615    });
 6616    editor.update(cx, |editor, cx| {
 6617        assert_text_with_selections(
 6618            editor,
 6619            indoc! {r#"
 6620                use mod1::mod2::{mod3, mo«ˇ»d4};
 6621
 6622                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6623                    let var1 = "te«ˇ»xt";
 6624                }
 6625            "#},
 6626            cx,
 6627        );
 6628    });
 6629
 6630    // Trying to shrink the selected syntax node one more time has no effect.
 6631    editor.update_in(cx, |editor, window, cx| {
 6632        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6633    });
 6634    editor.update_in(cx, |editor, _, cx| {
 6635        assert_text_with_selections(
 6636            editor,
 6637            indoc! {r#"
 6638                use mod1::mod2::{mod3, mo«ˇ»d4};
 6639
 6640                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6641                    let var1 = "te«ˇ»xt";
 6642                }
 6643            "#},
 6644            cx,
 6645        );
 6646    });
 6647
 6648    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6649    // a fold.
 6650    editor.update_in(cx, |editor, window, cx| {
 6651        editor.fold_creases(
 6652            vec![
 6653                Crease::simple(
 6654                    Point::new(0, 21)..Point::new(0, 24),
 6655                    FoldPlaceholder::test(),
 6656                ),
 6657                Crease::simple(
 6658                    Point::new(3, 20)..Point::new(3, 22),
 6659                    FoldPlaceholder::test(),
 6660                ),
 6661            ],
 6662            true,
 6663            window,
 6664            cx,
 6665        );
 6666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6667    });
 6668    editor.update(cx, |editor, cx| {
 6669        assert_text_with_selections(
 6670            editor,
 6671            indoc! {r#"
 6672                use mod1::mod2::«{mod3, mod4}ˇ»;
 6673
 6674                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6675                    let var1 = "«ˇtext»";
 6676                }
 6677            "#},
 6678            cx,
 6679        );
 6680    });
 6681}
 6682
 6683#[gpui::test]
 6684async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6685    init_test(cx, |_| {});
 6686
 6687    let language = Arc::new(Language::new(
 6688        LanguageConfig::default(),
 6689        Some(tree_sitter_rust::LANGUAGE.into()),
 6690    ));
 6691
 6692    let text = "let a = 2;";
 6693
 6694    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6695    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6697
 6698    editor
 6699        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6700        .await;
 6701
 6702    // Test case 1: Cursor at end of word
 6703    editor.update_in(cx, |editor, window, cx| {
 6704        editor.change_selections(None, window, cx, |s| {
 6705            s.select_display_ranges([
 6706                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6707            ]);
 6708        });
 6709    });
 6710    editor.update(cx, |editor, cx| {
 6711        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6712    });
 6713    editor.update_in(cx, |editor, window, cx| {
 6714        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6715    });
 6716    editor.update(cx, |editor, cx| {
 6717        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 6718    });
 6719    editor.update_in(cx, |editor, window, cx| {
 6720        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6721    });
 6722    editor.update(cx, |editor, cx| {
 6723        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6724    });
 6725
 6726    // Test case 2: Cursor at end of statement
 6727    editor.update_in(cx, |editor, window, cx| {
 6728        editor.change_selections(None, window, cx, |s| {
 6729            s.select_display_ranges([
 6730                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 6731            ]);
 6732        });
 6733    });
 6734    editor.update(cx, |editor, cx| {
 6735        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 6736    });
 6737    editor.update_in(cx, |editor, window, cx| {
 6738        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6739    });
 6740    editor.update(cx, |editor, cx| {
 6741        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6742    });
 6743}
 6744
 6745#[gpui::test]
 6746async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6747    init_test(cx, |_| {});
 6748
 6749    let language = Arc::new(Language::new(
 6750        LanguageConfig::default(),
 6751        Some(tree_sitter_rust::LANGUAGE.into()),
 6752    ));
 6753
 6754    let text = r#"
 6755        use mod1::mod2::{mod3, mod4};
 6756
 6757        fn fn_1(param1: bool, param2: &str) {
 6758            let var1 = "hello world";
 6759        }
 6760    "#
 6761    .unindent();
 6762
 6763    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6764    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6765    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6766
 6767    editor
 6768        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6769        .await;
 6770
 6771    // Test 1: Cursor on a letter of a string word
 6772    editor.update_in(cx, |editor, window, cx| {
 6773        editor.change_selections(None, window, cx, |s| {
 6774            s.select_display_ranges([
 6775                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6776            ]);
 6777        });
 6778    });
 6779    editor.update_in(cx, |editor, window, cx| {
 6780        assert_text_with_selections(
 6781            editor,
 6782            indoc! {r#"
 6783                use mod1::mod2::{mod3, mod4};
 6784
 6785                fn fn_1(param1: bool, param2: &str) {
 6786                    let var1 = "hˇello world";
 6787                }
 6788            "#},
 6789            cx,
 6790        );
 6791        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6792        assert_text_with_selections(
 6793            editor,
 6794            indoc! {r#"
 6795                use mod1::mod2::{mod3, mod4};
 6796
 6797                fn fn_1(param1: bool, param2: &str) {
 6798                    let var1 = "«ˇhello» world";
 6799                }
 6800            "#},
 6801            cx,
 6802        );
 6803    });
 6804
 6805    // Test 2: Partial selection within a word
 6806    editor.update_in(cx, |editor, window, cx| {
 6807        editor.change_selections(None, window, cx, |s| {
 6808            s.select_display_ranges([
 6809                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 6810            ]);
 6811        });
 6812    });
 6813    editor.update_in(cx, |editor, window, cx| {
 6814        assert_text_with_selections(
 6815            editor,
 6816            indoc! {r#"
 6817                use mod1::mod2::{mod3, mod4};
 6818
 6819                fn fn_1(param1: bool, param2: &str) {
 6820                    let var1 = "h«elˇ»lo world";
 6821                }
 6822            "#},
 6823            cx,
 6824        );
 6825        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6826        assert_text_with_selections(
 6827            editor,
 6828            indoc! {r#"
 6829                use mod1::mod2::{mod3, mod4};
 6830
 6831                fn fn_1(param1: bool, param2: &str) {
 6832                    let var1 = "«ˇhello» world";
 6833                }
 6834            "#},
 6835            cx,
 6836        );
 6837    });
 6838
 6839    // Test 3: Complete word already selected
 6840    editor.update_in(cx, |editor, window, cx| {
 6841        editor.change_selections(None, window, cx, |s| {
 6842            s.select_display_ranges([
 6843                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 6844            ]);
 6845        });
 6846    });
 6847    editor.update_in(cx, |editor, window, cx| {
 6848        assert_text_with_selections(
 6849            editor,
 6850            indoc! {r#"
 6851                use mod1::mod2::{mod3, mod4};
 6852
 6853                fn fn_1(param1: bool, param2: &str) {
 6854                    let var1 = "«helloˇ» world";
 6855                }
 6856            "#},
 6857            cx,
 6858        );
 6859        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6860        assert_text_with_selections(
 6861            editor,
 6862            indoc! {r#"
 6863                use mod1::mod2::{mod3, mod4};
 6864
 6865                fn fn_1(param1: bool, param2: &str) {
 6866                    let var1 = "«hello worldˇ»";
 6867                }
 6868            "#},
 6869            cx,
 6870        );
 6871    });
 6872
 6873    // Test 4: Selection spanning across words
 6874    editor.update_in(cx, |editor, window, cx| {
 6875        editor.change_selections(None, window, cx, |s| {
 6876            s.select_display_ranges([
 6877                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 6878            ]);
 6879        });
 6880    });
 6881    editor.update_in(cx, |editor, window, cx| {
 6882        assert_text_with_selections(
 6883            editor,
 6884            indoc! {r#"
 6885                use mod1::mod2::{mod3, mod4};
 6886
 6887                fn fn_1(param1: bool, param2: &str) {
 6888                    let var1 = "hel«lo woˇ»rld";
 6889                }
 6890            "#},
 6891            cx,
 6892        );
 6893        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6894        assert_text_with_selections(
 6895            editor,
 6896            indoc! {r#"
 6897                use mod1::mod2::{mod3, mod4};
 6898
 6899                fn fn_1(param1: bool, param2: &str) {
 6900                    let var1 = "«ˇhello world»";
 6901                }
 6902            "#},
 6903            cx,
 6904        );
 6905    });
 6906
 6907    // Test 5: Expansion beyond string
 6908    editor.update_in(cx, |editor, window, cx| {
 6909        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6910        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6911        assert_text_with_selections(
 6912            editor,
 6913            indoc! {r#"
 6914                use mod1::mod2::{mod3, mod4};
 6915
 6916                fn fn_1(param1: bool, param2: &str) {
 6917                    «ˇlet var1 = "hello world";»
 6918                }
 6919            "#},
 6920            cx,
 6921        );
 6922    });
 6923}
 6924
 6925#[gpui::test]
 6926async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6927    init_test(cx, |_| {});
 6928
 6929    let base_text = r#"
 6930        impl A {
 6931            // this is an uncommitted comment
 6932
 6933            fn b() {
 6934                c();
 6935            }
 6936
 6937            // this is another uncommitted comment
 6938
 6939            fn d() {
 6940                // e
 6941                // f
 6942            }
 6943        }
 6944
 6945        fn g() {
 6946            // h
 6947        }
 6948    "#
 6949    .unindent();
 6950
 6951    let text = r#"
 6952        ˇimpl A {
 6953
 6954            fn b() {
 6955                c();
 6956            }
 6957
 6958            fn d() {
 6959                // e
 6960                // f
 6961            }
 6962        }
 6963
 6964        fn g() {
 6965            // h
 6966        }
 6967    "#
 6968    .unindent();
 6969
 6970    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6971    cx.set_state(&text);
 6972    cx.set_head_text(&base_text);
 6973    cx.update_editor(|editor, window, cx| {
 6974        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6975    });
 6976
 6977    cx.assert_state_with_diff(
 6978        "
 6979        ˇimpl A {
 6980      -     // this is an uncommitted comment
 6981
 6982            fn b() {
 6983                c();
 6984            }
 6985
 6986      -     // this is another uncommitted comment
 6987      -
 6988            fn d() {
 6989                // e
 6990                // f
 6991            }
 6992        }
 6993
 6994        fn g() {
 6995            // h
 6996        }
 6997    "
 6998        .unindent(),
 6999    );
 7000
 7001    let expected_display_text = "
 7002        impl A {
 7003            // this is an uncommitted comment
 7004
 7005            fn b() {
 7006 7007            }
 7008
 7009            // this is another uncommitted comment
 7010
 7011            fn d() {
 7012 7013            }
 7014        }
 7015
 7016        fn g() {
 7017 7018        }
 7019        "
 7020    .unindent();
 7021
 7022    cx.update_editor(|editor, window, cx| {
 7023        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7024        assert_eq!(editor.display_text(cx), expected_display_text);
 7025    });
 7026}
 7027
 7028#[gpui::test]
 7029async fn test_autoindent(cx: &mut TestAppContext) {
 7030    init_test(cx, |_| {});
 7031
 7032    let language = Arc::new(
 7033        Language::new(
 7034            LanguageConfig {
 7035                brackets: BracketPairConfig {
 7036                    pairs: vec![
 7037                        BracketPair {
 7038                            start: "{".to_string(),
 7039                            end: "}".to_string(),
 7040                            close: false,
 7041                            surround: false,
 7042                            newline: true,
 7043                        },
 7044                        BracketPair {
 7045                            start: "(".to_string(),
 7046                            end: ")".to_string(),
 7047                            close: false,
 7048                            surround: false,
 7049                            newline: true,
 7050                        },
 7051                    ],
 7052                    ..Default::default()
 7053                },
 7054                ..Default::default()
 7055            },
 7056            Some(tree_sitter_rust::LANGUAGE.into()),
 7057        )
 7058        .with_indents_query(
 7059            r#"
 7060                (_ "(" ")" @end) @indent
 7061                (_ "{" "}" @end) @indent
 7062            "#,
 7063        )
 7064        .unwrap(),
 7065    );
 7066
 7067    let text = "fn a() {}";
 7068
 7069    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7070    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7071    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7072    editor
 7073        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7074        .await;
 7075
 7076    editor.update_in(cx, |editor, window, cx| {
 7077        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7078        editor.newline(&Newline, window, cx);
 7079        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7080        assert_eq!(
 7081            editor.selections.ranges(cx),
 7082            &[
 7083                Point::new(1, 4)..Point::new(1, 4),
 7084                Point::new(3, 4)..Point::new(3, 4),
 7085                Point::new(5, 0)..Point::new(5, 0)
 7086            ]
 7087        );
 7088    });
 7089}
 7090
 7091#[gpui::test]
 7092async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7093    init_test(cx, |_| {});
 7094
 7095    {
 7096        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7097        cx.set_state(indoc! {"
 7098            impl A {
 7099
 7100                fn b() {}
 7101
 7102            «fn c() {
 7103
 7104            }ˇ»
 7105            }
 7106        "});
 7107
 7108        cx.update_editor(|editor, window, cx| {
 7109            editor.autoindent(&Default::default(), window, cx);
 7110        });
 7111
 7112        cx.assert_editor_state(indoc! {"
 7113            impl A {
 7114
 7115                fn b() {}
 7116
 7117                «fn c() {
 7118
 7119                }ˇ»
 7120            }
 7121        "});
 7122    }
 7123
 7124    {
 7125        let mut cx = EditorTestContext::new_multibuffer(
 7126            cx,
 7127            [indoc! { "
 7128                impl A {
 7129                «
 7130                // a
 7131                fn b(){}
 7132                »
 7133                «
 7134                    }
 7135                    fn c(){}
 7136                »
 7137            "}],
 7138        );
 7139
 7140        let buffer = cx.update_editor(|editor, _, cx| {
 7141            let buffer = editor.buffer().update(cx, |buffer, _| {
 7142                buffer.all_buffers().iter().next().unwrap().clone()
 7143            });
 7144            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7145            buffer
 7146        });
 7147
 7148        cx.run_until_parked();
 7149        cx.update_editor(|editor, window, cx| {
 7150            editor.select_all(&Default::default(), window, cx);
 7151            editor.autoindent(&Default::default(), window, cx)
 7152        });
 7153        cx.run_until_parked();
 7154
 7155        cx.update(|_, cx| {
 7156            assert_eq!(
 7157                buffer.read(cx).text(),
 7158                indoc! { "
 7159                    impl A {
 7160
 7161                        // a
 7162                        fn b(){}
 7163
 7164
 7165                    }
 7166                    fn c(){}
 7167
 7168                " }
 7169            )
 7170        });
 7171    }
 7172}
 7173
 7174#[gpui::test]
 7175async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7176    init_test(cx, |_| {});
 7177
 7178    let mut cx = EditorTestContext::new(cx).await;
 7179
 7180    let language = Arc::new(Language::new(
 7181        LanguageConfig {
 7182            brackets: BracketPairConfig {
 7183                pairs: vec![
 7184                    BracketPair {
 7185                        start: "{".to_string(),
 7186                        end: "}".to_string(),
 7187                        close: true,
 7188                        surround: true,
 7189                        newline: true,
 7190                    },
 7191                    BracketPair {
 7192                        start: "(".to_string(),
 7193                        end: ")".to_string(),
 7194                        close: true,
 7195                        surround: true,
 7196                        newline: true,
 7197                    },
 7198                    BracketPair {
 7199                        start: "/*".to_string(),
 7200                        end: " */".to_string(),
 7201                        close: true,
 7202                        surround: true,
 7203                        newline: true,
 7204                    },
 7205                    BracketPair {
 7206                        start: "[".to_string(),
 7207                        end: "]".to_string(),
 7208                        close: false,
 7209                        surround: false,
 7210                        newline: true,
 7211                    },
 7212                    BracketPair {
 7213                        start: "\"".to_string(),
 7214                        end: "\"".to_string(),
 7215                        close: true,
 7216                        surround: true,
 7217                        newline: false,
 7218                    },
 7219                    BracketPair {
 7220                        start: "<".to_string(),
 7221                        end: ">".to_string(),
 7222                        close: false,
 7223                        surround: true,
 7224                        newline: true,
 7225                    },
 7226                ],
 7227                ..Default::default()
 7228            },
 7229            autoclose_before: "})]".to_string(),
 7230            ..Default::default()
 7231        },
 7232        Some(tree_sitter_rust::LANGUAGE.into()),
 7233    ));
 7234
 7235    cx.language_registry().add(language.clone());
 7236    cx.update_buffer(|buffer, cx| {
 7237        buffer.set_language(Some(language), cx);
 7238    });
 7239
 7240    cx.set_state(
 7241        &r#"
 7242            🏀ˇ
 7243            εˇ
 7244            ❤️ˇ
 7245        "#
 7246        .unindent(),
 7247    );
 7248
 7249    // autoclose multiple nested brackets at multiple cursors
 7250    cx.update_editor(|editor, window, cx| {
 7251        editor.handle_input("{", window, cx);
 7252        editor.handle_input("{", window, cx);
 7253        editor.handle_input("{", window, cx);
 7254    });
 7255    cx.assert_editor_state(
 7256        &"
 7257            🏀{{{ˇ}}}
 7258            ε{{{ˇ}}}
 7259            ❤️{{{ˇ}}}
 7260        "
 7261        .unindent(),
 7262    );
 7263
 7264    // insert a different closing bracket
 7265    cx.update_editor(|editor, window, cx| {
 7266        editor.handle_input(")", window, cx);
 7267    });
 7268    cx.assert_editor_state(
 7269        &"
 7270            🏀{{{)ˇ}}}
 7271            ε{{{)ˇ}}}
 7272            ❤️{{{)ˇ}}}
 7273        "
 7274        .unindent(),
 7275    );
 7276
 7277    // skip over the auto-closed brackets when typing a closing bracket
 7278    cx.update_editor(|editor, window, cx| {
 7279        editor.move_right(&MoveRight, window, cx);
 7280        editor.handle_input("}", window, cx);
 7281        editor.handle_input("}", window, cx);
 7282        editor.handle_input("}", window, cx);
 7283    });
 7284    cx.assert_editor_state(
 7285        &"
 7286            🏀{{{)}}}}ˇ
 7287            ε{{{)}}}}ˇ
 7288            ❤️{{{)}}}}ˇ
 7289        "
 7290        .unindent(),
 7291    );
 7292
 7293    // autoclose multi-character pairs
 7294    cx.set_state(
 7295        &"
 7296            ˇ
 7297            ˇ
 7298        "
 7299        .unindent(),
 7300    );
 7301    cx.update_editor(|editor, window, cx| {
 7302        editor.handle_input("/", window, cx);
 7303        editor.handle_input("*", window, cx);
 7304    });
 7305    cx.assert_editor_state(
 7306        &"
 7307            /*ˇ */
 7308            /*ˇ */
 7309        "
 7310        .unindent(),
 7311    );
 7312
 7313    // one cursor autocloses a multi-character pair, one cursor
 7314    // does not autoclose.
 7315    cx.set_state(
 7316        &"
 7317 7318            ˇ
 7319        "
 7320        .unindent(),
 7321    );
 7322    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7323    cx.assert_editor_state(
 7324        &"
 7325            /*ˇ */
 7326 7327        "
 7328        .unindent(),
 7329    );
 7330
 7331    // Don't autoclose if the next character isn't whitespace and isn't
 7332    // listed in the language's "autoclose_before" section.
 7333    cx.set_state("ˇa b");
 7334    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7335    cx.assert_editor_state("{ˇa b");
 7336
 7337    // Don't autoclose if `close` is false for the bracket pair
 7338    cx.set_state("ˇ");
 7339    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7340    cx.assert_editor_state("");
 7341
 7342    // Surround with brackets if text is selected
 7343    cx.set_state("«aˇ» b");
 7344    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7345    cx.assert_editor_state("{«aˇ»} b");
 7346
 7347    // Autoclose when not immediately after a word character
 7348    cx.set_state("a ˇ");
 7349    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7350    cx.assert_editor_state("a \"ˇ\"");
 7351
 7352    // Autoclose pair where the start and end characters are the same
 7353    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7354    cx.assert_editor_state("a \"\"ˇ");
 7355
 7356    // Don't autoclose when immediately after a word character
 7357    cx.set_state("");
 7358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7359    cx.assert_editor_state("a\"ˇ");
 7360
 7361    // Do autoclose when after a non-word character
 7362    cx.set_state("");
 7363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7364    cx.assert_editor_state("{\"ˇ\"");
 7365
 7366    // Non identical pairs autoclose regardless of preceding character
 7367    cx.set_state("");
 7368    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7369    cx.assert_editor_state("a{ˇ}");
 7370
 7371    // Don't autoclose pair if autoclose is disabled
 7372    cx.set_state("ˇ");
 7373    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7374    cx.assert_editor_state("");
 7375
 7376    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7377    cx.set_state("«aˇ» b");
 7378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7379    cx.assert_editor_state("<«aˇ»> b");
 7380}
 7381
 7382#[gpui::test]
 7383async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7384    init_test(cx, |settings| {
 7385        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7386    });
 7387
 7388    let mut cx = EditorTestContext::new(cx).await;
 7389
 7390    let language = Arc::new(Language::new(
 7391        LanguageConfig {
 7392            brackets: BracketPairConfig {
 7393                pairs: vec![
 7394                    BracketPair {
 7395                        start: "{".to_string(),
 7396                        end: "}".to_string(),
 7397                        close: true,
 7398                        surround: true,
 7399                        newline: true,
 7400                    },
 7401                    BracketPair {
 7402                        start: "(".to_string(),
 7403                        end: ")".to_string(),
 7404                        close: true,
 7405                        surround: true,
 7406                        newline: true,
 7407                    },
 7408                    BracketPair {
 7409                        start: "[".to_string(),
 7410                        end: "]".to_string(),
 7411                        close: false,
 7412                        surround: false,
 7413                        newline: true,
 7414                    },
 7415                ],
 7416                ..Default::default()
 7417            },
 7418            autoclose_before: "})]".to_string(),
 7419            ..Default::default()
 7420        },
 7421        Some(tree_sitter_rust::LANGUAGE.into()),
 7422    ));
 7423
 7424    cx.language_registry().add(language.clone());
 7425    cx.update_buffer(|buffer, cx| {
 7426        buffer.set_language(Some(language), cx);
 7427    });
 7428
 7429    cx.set_state(
 7430        &"
 7431            ˇ
 7432            ˇ
 7433            ˇ
 7434        "
 7435        .unindent(),
 7436    );
 7437
 7438    // ensure only matching closing brackets are skipped over
 7439    cx.update_editor(|editor, window, cx| {
 7440        editor.handle_input("}", window, cx);
 7441        editor.move_left(&MoveLeft, window, cx);
 7442        editor.handle_input(")", window, cx);
 7443        editor.move_left(&MoveLeft, window, cx);
 7444    });
 7445    cx.assert_editor_state(
 7446        &"
 7447            ˇ)}
 7448            ˇ)}
 7449            ˇ)}
 7450        "
 7451        .unindent(),
 7452    );
 7453
 7454    // skip-over closing brackets at multiple cursors
 7455    cx.update_editor(|editor, window, cx| {
 7456        editor.handle_input(")", window, cx);
 7457        editor.handle_input("}", window, cx);
 7458    });
 7459    cx.assert_editor_state(
 7460        &"
 7461            )}ˇ
 7462            )}ˇ
 7463            )}ˇ
 7464        "
 7465        .unindent(),
 7466    );
 7467
 7468    // ignore non-close brackets
 7469    cx.update_editor(|editor, window, cx| {
 7470        editor.handle_input("]", window, cx);
 7471        editor.move_left(&MoveLeft, window, cx);
 7472        editor.handle_input("]", window, cx);
 7473    });
 7474    cx.assert_editor_state(
 7475        &"
 7476            )}]ˇ]
 7477            )}]ˇ]
 7478            )}]ˇ]
 7479        "
 7480        .unindent(),
 7481    );
 7482}
 7483
 7484#[gpui::test]
 7485async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7486    init_test(cx, |_| {});
 7487
 7488    let mut cx = EditorTestContext::new(cx).await;
 7489
 7490    let html_language = Arc::new(
 7491        Language::new(
 7492            LanguageConfig {
 7493                name: "HTML".into(),
 7494                brackets: BracketPairConfig {
 7495                    pairs: vec![
 7496                        BracketPair {
 7497                            start: "<".into(),
 7498                            end: ">".into(),
 7499                            close: true,
 7500                            ..Default::default()
 7501                        },
 7502                        BracketPair {
 7503                            start: "{".into(),
 7504                            end: "}".into(),
 7505                            close: true,
 7506                            ..Default::default()
 7507                        },
 7508                        BracketPair {
 7509                            start: "(".into(),
 7510                            end: ")".into(),
 7511                            close: true,
 7512                            ..Default::default()
 7513                        },
 7514                    ],
 7515                    ..Default::default()
 7516                },
 7517                autoclose_before: "})]>".into(),
 7518                ..Default::default()
 7519            },
 7520            Some(tree_sitter_html::LANGUAGE.into()),
 7521        )
 7522        .with_injection_query(
 7523            r#"
 7524            (script_element
 7525                (raw_text) @injection.content
 7526                (#set! injection.language "javascript"))
 7527            "#,
 7528        )
 7529        .unwrap(),
 7530    );
 7531
 7532    let javascript_language = Arc::new(Language::new(
 7533        LanguageConfig {
 7534            name: "JavaScript".into(),
 7535            brackets: BracketPairConfig {
 7536                pairs: vec![
 7537                    BracketPair {
 7538                        start: "/*".into(),
 7539                        end: " */".into(),
 7540                        close: true,
 7541                        ..Default::default()
 7542                    },
 7543                    BracketPair {
 7544                        start: "{".into(),
 7545                        end: "}".into(),
 7546                        close: true,
 7547                        ..Default::default()
 7548                    },
 7549                    BracketPair {
 7550                        start: "(".into(),
 7551                        end: ")".into(),
 7552                        close: true,
 7553                        ..Default::default()
 7554                    },
 7555                ],
 7556                ..Default::default()
 7557            },
 7558            autoclose_before: "})]>".into(),
 7559            ..Default::default()
 7560        },
 7561        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7562    ));
 7563
 7564    cx.language_registry().add(html_language.clone());
 7565    cx.language_registry().add(javascript_language.clone());
 7566
 7567    cx.update_buffer(|buffer, cx| {
 7568        buffer.set_language(Some(html_language), cx);
 7569    });
 7570
 7571    cx.set_state(
 7572        &r#"
 7573            <body>ˇ
 7574                <script>
 7575                    var x = 1;ˇ
 7576                </script>
 7577            </body>ˇ
 7578        "#
 7579        .unindent(),
 7580    );
 7581
 7582    // Precondition: different languages are active at different locations.
 7583    cx.update_editor(|editor, window, cx| {
 7584        let snapshot = editor.snapshot(window, cx);
 7585        let cursors = editor.selections.ranges::<usize>(cx);
 7586        let languages = cursors
 7587            .iter()
 7588            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7589            .collect::<Vec<_>>();
 7590        assert_eq!(
 7591            languages,
 7592            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7593        );
 7594    });
 7595
 7596    // Angle brackets autoclose in HTML, but not JavaScript.
 7597    cx.update_editor(|editor, window, cx| {
 7598        editor.handle_input("<", window, cx);
 7599        editor.handle_input("a", window, cx);
 7600    });
 7601    cx.assert_editor_state(
 7602        &r#"
 7603            <body><aˇ>
 7604                <script>
 7605                    var x = 1;<aˇ
 7606                </script>
 7607            </body><aˇ>
 7608        "#
 7609        .unindent(),
 7610    );
 7611
 7612    // Curly braces and parens autoclose in both HTML and JavaScript.
 7613    cx.update_editor(|editor, window, cx| {
 7614        editor.handle_input(" b=", window, cx);
 7615        editor.handle_input("{", window, cx);
 7616        editor.handle_input("c", window, cx);
 7617        editor.handle_input("(", window, cx);
 7618    });
 7619    cx.assert_editor_state(
 7620        &r#"
 7621            <body><a b={c(ˇ)}>
 7622                <script>
 7623                    var x = 1;<a b={c(ˇ)}
 7624                </script>
 7625            </body><a b={c(ˇ)}>
 7626        "#
 7627        .unindent(),
 7628    );
 7629
 7630    // Brackets that were already autoclosed are skipped.
 7631    cx.update_editor(|editor, window, cx| {
 7632        editor.handle_input(")", window, cx);
 7633        editor.handle_input("d", window, cx);
 7634        editor.handle_input("}", window, cx);
 7635    });
 7636    cx.assert_editor_state(
 7637        &r#"
 7638            <body><a b={c()d}ˇ>
 7639                <script>
 7640                    var x = 1;<a b={c()d}ˇ
 7641                </script>
 7642            </body><a b={c()d}ˇ>
 7643        "#
 7644        .unindent(),
 7645    );
 7646    cx.update_editor(|editor, window, cx| {
 7647        editor.handle_input(">", window, cx);
 7648    });
 7649    cx.assert_editor_state(
 7650        &r#"
 7651            <body><a b={c()d}>ˇ
 7652                <script>
 7653                    var x = 1;<a b={c()d}>ˇ
 7654                </script>
 7655            </body><a b={c()d}>ˇ
 7656        "#
 7657        .unindent(),
 7658    );
 7659
 7660    // Reset
 7661    cx.set_state(
 7662        &r#"
 7663            <body>ˇ
 7664                <script>
 7665                    var x = 1;ˇ
 7666                </script>
 7667            </body>ˇ
 7668        "#
 7669        .unindent(),
 7670    );
 7671
 7672    cx.update_editor(|editor, window, cx| {
 7673        editor.handle_input("<", window, cx);
 7674    });
 7675    cx.assert_editor_state(
 7676        &r#"
 7677            <body><ˇ>
 7678                <script>
 7679                    var x = 1;<ˇ
 7680                </script>
 7681            </body><ˇ>
 7682        "#
 7683        .unindent(),
 7684    );
 7685
 7686    // When backspacing, the closing angle brackets are removed.
 7687    cx.update_editor(|editor, window, cx| {
 7688        editor.backspace(&Backspace, window, cx);
 7689    });
 7690    cx.assert_editor_state(
 7691        &r#"
 7692            <body>ˇ
 7693                <script>
 7694                    var x = 1;ˇ
 7695                </script>
 7696            </body>ˇ
 7697        "#
 7698        .unindent(),
 7699    );
 7700
 7701    // Block comments autoclose in JavaScript, but not HTML.
 7702    cx.update_editor(|editor, window, cx| {
 7703        editor.handle_input("/", window, cx);
 7704        editor.handle_input("*", window, cx);
 7705    });
 7706    cx.assert_editor_state(
 7707        &r#"
 7708            <body>/*ˇ
 7709                <script>
 7710                    var x = 1;/*ˇ */
 7711                </script>
 7712            </body>/*ˇ
 7713        "#
 7714        .unindent(),
 7715    );
 7716}
 7717
 7718#[gpui::test]
 7719async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7720    init_test(cx, |_| {});
 7721
 7722    let mut cx = EditorTestContext::new(cx).await;
 7723
 7724    let rust_language = Arc::new(
 7725        Language::new(
 7726            LanguageConfig {
 7727                name: "Rust".into(),
 7728                brackets: serde_json::from_value(json!([
 7729                    { "start": "{", "end": "}", "close": true, "newline": true },
 7730                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7731                ]))
 7732                .unwrap(),
 7733                autoclose_before: "})]>".into(),
 7734                ..Default::default()
 7735            },
 7736            Some(tree_sitter_rust::LANGUAGE.into()),
 7737        )
 7738        .with_override_query("(string_literal) @string")
 7739        .unwrap(),
 7740    );
 7741
 7742    cx.language_registry().add(rust_language.clone());
 7743    cx.update_buffer(|buffer, cx| {
 7744        buffer.set_language(Some(rust_language), cx);
 7745    });
 7746
 7747    cx.set_state(
 7748        &r#"
 7749            let x = ˇ
 7750        "#
 7751        .unindent(),
 7752    );
 7753
 7754    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7755    cx.update_editor(|editor, window, cx| {
 7756        editor.handle_input("\"", window, cx);
 7757    });
 7758    cx.assert_editor_state(
 7759        &r#"
 7760            let x = "ˇ"
 7761        "#
 7762        .unindent(),
 7763    );
 7764
 7765    // Inserting another quotation mark. The cursor moves across the existing
 7766    // automatically-inserted quotation mark.
 7767    cx.update_editor(|editor, window, cx| {
 7768        editor.handle_input("\"", window, cx);
 7769    });
 7770    cx.assert_editor_state(
 7771        &r#"
 7772            let x = ""ˇ
 7773        "#
 7774        .unindent(),
 7775    );
 7776
 7777    // Reset
 7778    cx.set_state(
 7779        &r#"
 7780            let x = ˇ
 7781        "#
 7782        .unindent(),
 7783    );
 7784
 7785    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7786    cx.update_editor(|editor, window, cx| {
 7787        editor.handle_input("\"", window, cx);
 7788        editor.handle_input(" ", window, cx);
 7789        editor.move_left(&Default::default(), window, cx);
 7790        editor.handle_input("\\", window, cx);
 7791        editor.handle_input("\"", window, cx);
 7792    });
 7793    cx.assert_editor_state(
 7794        &r#"
 7795            let x = "\"ˇ "
 7796        "#
 7797        .unindent(),
 7798    );
 7799
 7800    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7801    // mark. Nothing is inserted.
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.move_right(&Default::default(), window, cx);
 7804        editor.handle_input("\"", window, cx);
 7805    });
 7806    cx.assert_editor_state(
 7807        &r#"
 7808            let x = "\" "ˇ
 7809        "#
 7810        .unindent(),
 7811    );
 7812}
 7813
 7814#[gpui::test]
 7815async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7816    init_test(cx, |_| {});
 7817
 7818    let language = Arc::new(Language::new(
 7819        LanguageConfig {
 7820            brackets: BracketPairConfig {
 7821                pairs: vec![
 7822                    BracketPair {
 7823                        start: "{".to_string(),
 7824                        end: "}".to_string(),
 7825                        close: true,
 7826                        surround: true,
 7827                        newline: true,
 7828                    },
 7829                    BracketPair {
 7830                        start: "/* ".to_string(),
 7831                        end: "*/".to_string(),
 7832                        close: true,
 7833                        surround: true,
 7834                        ..Default::default()
 7835                    },
 7836                ],
 7837                ..Default::default()
 7838            },
 7839            ..Default::default()
 7840        },
 7841        Some(tree_sitter_rust::LANGUAGE.into()),
 7842    ));
 7843
 7844    let text = r#"
 7845        a
 7846        b
 7847        c
 7848    "#
 7849    .unindent();
 7850
 7851    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7852    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7853    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7854    editor
 7855        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7856        .await;
 7857
 7858    editor.update_in(cx, |editor, window, cx| {
 7859        editor.change_selections(None, window, cx, |s| {
 7860            s.select_display_ranges([
 7861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7863                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7864            ])
 7865        });
 7866
 7867        editor.handle_input("{", window, cx);
 7868        editor.handle_input("{", window, cx);
 7869        editor.handle_input("{", window, cx);
 7870        assert_eq!(
 7871            editor.text(cx),
 7872            "
 7873                {{{a}}}
 7874                {{{b}}}
 7875                {{{c}}}
 7876            "
 7877            .unindent()
 7878        );
 7879        assert_eq!(
 7880            editor.selections.display_ranges(cx),
 7881            [
 7882                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7883                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7884                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7885            ]
 7886        );
 7887
 7888        editor.undo(&Undo, window, cx);
 7889        editor.undo(&Undo, window, cx);
 7890        editor.undo(&Undo, window, cx);
 7891        assert_eq!(
 7892            editor.text(cx),
 7893            "
 7894                a
 7895                b
 7896                c
 7897            "
 7898            .unindent()
 7899        );
 7900        assert_eq!(
 7901            editor.selections.display_ranges(cx),
 7902            [
 7903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7904                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7905                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7906            ]
 7907        );
 7908
 7909        // Ensure inserting the first character of a multi-byte bracket pair
 7910        // doesn't surround the selections with the bracket.
 7911        editor.handle_input("/", window, cx);
 7912        assert_eq!(
 7913            editor.text(cx),
 7914            "
 7915                /
 7916                /
 7917                /
 7918            "
 7919            .unindent()
 7920        );
 7921        assert_eq!(
 7922            editor.selections.display_ranges(cx),
 7923            [
 7924                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7925                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7926                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7927            ]
 7928        );
 7929
 7930        editor.undo(&Undo, window, cx);
 7931        assert_eq!(
 7932            editor.text(cx),
 7933            "
 7934                a
 7935                b
 7936                c
 7937            "
 7938            .unindent()
 7939        );
 7940        assert_eq!(
 7941            editor.selections.display_ranges(cx),
 7942            [
 7943                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7944                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7945                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7946            ]
 7947        );
 7948
 7949        // Ensure inserting the last character of a multi-byte bracket pair
 7950        // doesn't surround the selections with the bracket.
 7951        editor.handle_input("*", window, cx);
 7952        assert_eq!(
 7953            editor.text(cx),
 7954            "
 7955                *
 7956                *
 7957                *
 7958            "
 7959            .unindent()
 7960        );
 7961        assert_eq!(
 7962            editor.selections.display_ranges(cx),
 7963            [
 7964                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7965                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7966                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7967            ]
 7968        );
 7969    });
 7970}
 7971
 7972#[gpui::test]
 7973async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7974    init_test(cx, |_| {});
 7975
 7976    let language = Arc::new(Language::new(
 7977        LanguageConfig {
 7978            brackets: BracketPairConfig {
 7979                pairs: vec![BracketPair {
 7980                    start: "{".to_string(),
 7981                    end: "}".to_string(),
 7982                    close: true,
 7983                    surround: true,
 7984                    newline: true,
 7985                }],
 7986                ..Default::default()
 7987            },
 7988            autoclose_before: "}".to_string(),
 7989            ..Default::default()
 7990        },
 7991        Some(tree_sitter_rust::LANGUAGE.into()),
 7992    ));
 7993
 7994    let text = r#"
 7995        a
 7996        b
 7997        c
 7998    "#
 7999    .unindent();
 8000
 8001    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8003    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8004    editor
 8005        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8006        .await;
 8007
 8008    editor.update_in(cx, |editor, window, cx| {
 8009        editor.change_selections(None, window, cx, |s| {
 8010            s.select_ranges([
 8011                Point::new(0, 1)..Point::new(0, 1),
 8012                Point::new(1, 1)..Point::new(1, 1),
 8013                Point::new(2, 1)..Point::new(2, 1),
 8014            ])
 8015        });
 8016
 8017        editor.handle_input("{", window, cx);
 8018        editor.handle_input("{", window, cx);
 8019        editor.handle_input("_", window, cx);
 8020        assert_eq!(
 8021            editor.text(cx),
 8022            "
 8023                a{{_}}
 8024                b{{_}}
 8025                c{{_}}
 8026            "
 8027            .unindent()
 8028        );
 8029        assert_eq!(
 8030            editor.selections.ranges::<Point>(cx),
 8031            [
 8032                Point::new(0, 4)..Point::new(0, 4),
 8033                Point::new(1, 4)..Point::new(1, 4),
 8034                Point::new(2, 4)..Point::new(2, 4)
 8035            ]
 8036        );
 8037
 8038        editor.backspace(&Default::default(), window, cx);
 8039        editor.backspace(&Default::default(), window, cx);
 8040        assert_eq!(
 8041            editor.text(cx),
 8042            "
 8043                a{}
 8044                b{}
 8045                c{}
 8046            "
 8047            .unindent()
 8048        );
 8049        assert_eq!(
 8050            editor.selections.ranges::<Point>(cx),
 8051            [
 8052                Point::new(0, 2)..Point::new(0, 2),
 8053                Point::new(1, 2)..Point::new(1, 2),
 8054                Point::new(2, 2)..Point::new(2, 2)
 8055            ]
 8056        );
 8057
 8058        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8059        assert_eq!(
 8060            editor.text(cx),
 8061            "
 8062                a
 8063                b
 8064                c
 8065            "
 8066            .unindent()
 8067        );
 8068        assert_eq!(
 8069            editor.selections.ranges::<Point>(cx),
 8070            [
 8071                Point::new(0, 1)..Point::new(0, 1),
 8072                Point::new(1, 1)..Point::new(1, 1),
 8073                Point::new(2, 1)..Point::new(2, 1)
 8074            ]
 8075        );
 8076    });
 8077}
 8078
 8079#[gpui::test]
 8080async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8081    init_test(cx, |settings| {
 8082        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8083    });
 8084
 8085    let mut cx = EditorTestContext::new(cx).await;
 8086
 8087    let language = Arc::new(Language::new(
 8088        LanguageConfig {
 8089            brackets: BracketPairConfig {
 8090                pairs: vec![
 8091                    BracketPair {
 8092                        start: "{".to_string(),
 8093                        end: "}".to_string(),
 8094                        close: true,
 8095                        surround: true,
 8096                        newline: true,
 8097                    },
 8098                    BracketPair {
 8099                        start: "(".to_string(),
 8100                        end: ")".to_string(),
 8101                        close: true,
 8102                        surround: true,
 8103                        newline: true,
 8104                    },
 8105                    BracketPair {
 8106                        start: "[".to_string(),
 8107                        end: "]".to_string(),
 8108                        close: false,
 8109                        surround: true,
 8110                        newline: true,
 8111                    },
 8112                ],
 8113                ..Default::default()
 8114            },
 8115            autoclose_before: "})]".to_string(),
 8116            ..Default::default()
 8117        },
 8118        Some(tree_sitter_rust::LANGUAGE.into()),
 8119    ));
 8120
 8121    cx.language_registry().add(language.clone());
 8122    cx.update_buffer(|buffer, cx| {
 8123        buffer.set_language(Some(language), cx);
 8124    });
 8125
 8126    cx.set_state(
 8127        &"
 8128            {(ˇ)}
 8129            [[ˇ]]
 8130            {(ˇ)}
 8131        "
 8132        .unindent(),
 8133    );
 8134
 8135    cx.update_editor(|editor, window, cx| {
 8136        editor.backspace(&Default::default(), window, cx);
 8137        editor.backspace(&Default::default(), window, cx);
 8138    });
 8139
 8140    cx.assert_editor_state(
 8141        &"
 8142            ˇ
 8143            ˇ]]
 8144            ˇ
 8145        "
 8146        .unindent(),
 8147    );
 8148
 8149    cx.update_editor(|editor, window, cx| {
 8150        editor.handle_input("{", window, cx);
 8151        editor.handle_input("{", window, cx);
 8152        editor.move_right(&MoveRight, window, cx);
 8153        editor.move_right(&MoveRight, window, cx);
 8154        editor.move_left(&MoveLeft, window, cx);
 8155        editor.move_left(&MoveLeft, window, cx);
 8156        editor.backspace(&Default::default(), window, cx);
 8157    });
 8158
 8159    cx.assert_editor_state(
 8160        &"
 8161            {ˇ}
 8162            {ˇ}]]
 8163            {ˇ}
 8164        "
 8165        .unindent(),
 8166    );
 8167
 8168    cx.update_editor(|editor, window, cx| {
 8169        editor.backspace(&Default::default(), window, cx);
 8170    });
 8171
 8172    cx.assert_editor_state(
 8173        &"
 8174            ˇ
 8175            ˇ]]
 8176            ˇ
 8177        "
 8178        .unindent(),
 8179    );
 8180}
 8181
 8182#[gpui::test]
 8183async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8184    init_test(cx, |_| {});
 8185
 8186    let language = Arc::new(Language::new(
 8187        LanguageConfig::default(),
 8188        Some(tree_sitter_rust::LANGUAGE.into()),
 8189    ));
 8190
 8191    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8192    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8193    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8194    editor
 8195        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8196        .await;
 8197
 8198    editor.update_in(cx, |editor, window, cx| {
 8199        editor.set_auto_replace_emoji_shortcode(true);
 8200
 8201        editor.handle_input("Hello ", window, cx);
 8202        editor.handle_input(":wave", window, cx);
 8203        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8204
 8205        editor.handle_input(":", window, cx);
 8206        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8207
 8208        editor.handle_input(" :smile", window, cx);
 8209        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8210
 8211        editor.handle_input(":", window, cx);
 8212        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8213
 8214        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8215        editor.handle_input(":wave", window, cx);
 8216        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8217
 8218        editor.handle_input(":", window, cx);
 8219        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8220
 8221        editor.handle_input(":1", window, cx);
 8222        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8223
 8224        editor.handle_input(":", window, cx);
 8225        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8226
 8227        // Ensure shortcode does not get replaced when it is part of a word
 8228        editor.handle_input(" Test:wave", window, cx);
 8229        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8230
 8231        editor.handle_input(":", window, cx);
 8232        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8233
 8234        editor.set_auto_replace_emoji_shortcode(false);
 8235
 8236        // Ensure shortcode does not get replaced when auto replace is off
 8237        editor.handle_input(" :wave", window, cx);
 8238        assert_eq!(
 8239            editor.text(cx),
 8240            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8241        );
 8242
 8243        editor.handle_input(":", window, cx);
 8244        assert_eq!(
 8245            editor.text(cx),
 8246            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8247        );
 8248    });
 8249}
 8250
 8251#[gpui::test]
 8252async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8253    init_test(cx, |_| {});
 8254
 8255    let (text, insertion_ranges) = marked_text_ranges(
 8256        indoc! {"
 8257            ˇ
 8258        "},
 8259        false,
 8260    );
 8261
 8262    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8263    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8264
 8265    _ = editor.update_in(cx, |editor, window, cx| {
 8266        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8267
 8268        editor
 8269            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8270            .unwrap();
 8271
 8272        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8273            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8274            assert_eq!(editor.text(cx), expected_text);
 8275            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8276        }
 8277
 8278        assert(
 8279            editor,
 8280            cx,
 8281            indoc! {"
 8282            type «» =•
 8283            "},
 8284        );
 8285
 8286        assert!(editor.context_menu_visible(), "There should be a matches");
 8287    });
 8288}
 8289
 8290#[gpui::test]
 8291async fn test_snippets(cx: &mut TestAppContext) {
 8292    init_test(cx, |_| {});
 8293
 8294    let (text, insertion_ranges) = marked_text_ranges(
 8295        indoc! {"
 8296            a.ˇ b
 8297            a.ˇ b
 8298            a.ˇ b
 8299        "},
 8300        false,
 8301    );
 8302
 8303    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8304    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8305
 8306    editor.update_in(cx, |editor, window, cx| {
 8307        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8308
 8309        editor
 8310            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8311            .unwrap();
 8312
 8313        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8314            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8315            assert_eq!(editor.text(cx), expected_text);
 8316            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8317        }
 8318
 8319        assert(
 8320            editor,
 8321            cx,
 8322            indoc! {"
 8323                a.f(«one», two, «three») b
 8324                a.f(«one», two, «three») b
 8325                a.f(«one», two, «three») b
 8326            "},
 8327        );
 8328
 8329        // Can't move earlier than the first tab stop
 8330        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 8331        assert(
 8332            editor,
 8333            cx,
 8334            indoc! {"
 8335                a.f(«one», two, «three») b
 8336                a.f(«one», two, «three») b
 8337                a.f(«one», two, «three») b
 8338            "},
 8339        );
 8340
 8341        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8342        assert(
 8343            editor,
 8344            cx,
 8345            indoc! {"
 8346                a.f(one, «two», three) b
 8347                a.f(one, «two», three) b
 8348                a.f(one, «two», three) b
 8349            "},
 8350        );
 8351
 8352        editor.move_to_prev_snippet_tabstop(window, cx);
 8353        assert(
 8354            editor,
 8355            cx,
 8356            indoc! {"
 8357                a.f(«one», two, «three») b
 8358                a.f(«one», two, «three») b
 8359                a.f(«one», two, «three») b
 8360            "},
 8361        );
 8362
 8363        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8364        assert(
 8365            editor,
 8366            cx,
 8367            indoc! {"
 8368                a.f(one, «two», three) b
 8369                a.f(one, «two», three) b
 8370                a.f(one, «two», three) b
 8371            "},
 8372        );
 8373        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8374        assert(
 8375            editor,
 8376            cx,
 8377            indoc! {"
 8378                a.f(one, two, three)ˇ b
 8379                a.f(one, two, three)ˇ b
 8380                a.f(one, two, three)ˇ b
 8381            "},
 8382        );
 8383
 8384        // As soon as the last tab stop is reached, snippet state is gone
 8385        editor.move_to_prev_snippet_tabstop(window, cx);
 8386        assert(
 8387            editor,
 8388            cx,
 8389            indoc! {"
 8390                a.f(one, two, three)ˇ b
 8391                a.f(one, two, three)ˇ b
 8392                a.f(one, two, three)ˇ b
 8393            "},
 8394        );
 8395    });
 8396}
 8397
 8398#[gpui::test]
 8399async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8400    init_test(cx, |_| {});
 8401
 8402    let fs = FakeFs::new(cx.executor());
 8403    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8404
 8405    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8406
 8407    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8408    language_registry.add(rust_lang());
 8409    let mut fake_servers = language_registry.register_fake_lsp(
 8410        "Rust",
 8411        FakeLspAdapter {
 8412            capabilities: lsp::ServerCapabilities {
 8413                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8414                ..Default::default()
 8415            },
 8416            ..Default::default()
 8417        },
 8418    );
 8419
 8420    let buffer = project
 8421        .update(cx, |project, cx| {
 8422            project.open_local_buffer(path!("/file.rs"), cx)
 8423        })
 8424        .await
 8425        .unwrap();
 8426
 8427    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8428    let (editor, cx) = cx.add_window_view(|window, cx| {
 8429        build_editor_with_project(project.clone(), buffer, window, cx)
 8430    });
 8431    editor.update_in(cx, |editor, window, cx| {
 8432        editor.set_text("one\ntwo\nthree\n", window, cx)
 8433    });
 8434    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8435
 8436    cx.executor().start_waiting();
 8437    let fake_server = fake_servers.next().await.unwrap();
 8438
 8439    {
 8440        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8441            move |params, _| async move {
 8442                assert_eq!(
 8443                    params.text_document.uri,
 8444                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8445                );
 8446                assert_eq!(params.options.tab_size, 4);
 8447                Ok(Some(vec![lsp::TextEdit::new(
 8448                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8449                    ", ".to_string(),
 8450                )]))
 8451            },
 8452        );
 8453        let save = editor
 8454            .update_in(cx, |editor, window, cx| {
 8455                editor.save(true, project.clone(), window, cx)
 8456            })
 8457            .unwrap();
 8458        cx.executor().start_waiting();
 8459        save.await;
 8460
 8461        assert_eq!(
 8462            editor.update(cx, |editor, cx| editor.text(cx)),
 8463            "one, two\nthree\n"
 8464        );
 8465        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8466    }
 8467
 8468    {
 8469        editor.update_in(cx, |editor, window, cx| {
 8470            editor.set_text("one\ntwo\nthree\n", window, cx)
 8471        });
 8472        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8473
 8474        // Ensure we can still save even if formatting hangs.
 8475        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8476            move |params, _| async move {
 8477                assert_eq!(
 8478                    params.text_document.uri,
 8479                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8480                );
 8481                futures::future::pending::<()>().await;
 8482                unreachable!()
 8483            },
 8484        );
 8485        let save = editor
 8486            .update_in(cx, |editor, window, cx| {
 8487                editor.save(true, project.clone(), window, cx)
 8488            })
 8489            .unwrap();
 8490        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8491        cx.executor().start_waiting();
 8492        save.await;
 8493        assert_eq!(
 8494            editor.update(cx, |editor, cx| editor.text(cx)),
 8495            "one\ntwo\nthree\n"
 8496        );
 8497    }
 8498
 8499    // For non-dirty buffer, no formatting request should be sent
 8500    {
 8501        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8502
 8503        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8504            panic!("Should not be invoked on non-dirty buffer");
 8505        });
 8506        let save = editor
 8507            .update_in(cx, |editor, window, cx| {
 8508                editor.save(true, project.clone(), window, cx)
 8509            })
 8510            .unwrap();
 8511        cx.executor().start_waiting();
 8512        save.await;
 8513    }
 8514
 8515    // Set rust language override and assert overridden tabsize is sent to language server
 8516    update_test_language_settings(cx, |settings| {
 8517        settings.languages.insert(
 8518            "Rust".into(),
 8519            LanguageSettingsContent {
 8520                tab_size: NonZeroU32::new(8),
 8521                ..Default::default()
 8522            },
 8523        );
 8524    });
 8525
 8526    {
 8527        editor.update_in(cx, |editor, window, cx| {
 8528            editor.set_text("somehting_new\n", window, cx)
 8529        });
 8530        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8531        let _formatting_request_signal = fake_server
 8532            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8533                assert_eq!(
 8534                    params.text_document.uri,
 8535                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8536                );
 8537                assert_eq!(params.options.tab_size, 8);
 8538                Ok(Some(vec![]))
 8539            });
 8540        let save = editor
 8541            .update_in(cx, |editor, window, cx| {
 8542                editor.save(true, project.clone(), window, cx)
 8543            })
 8544            .unwrap();
 8545        cx.executor().start_waiting();
 8546        save.await;
 8547    }
 8548}
 8549
 8550#[gpui::test]
 8551async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8552    init_test(cx, |_| {});
 8553
 8554    let cols = 4;
 8555    let rows = 10;
 8556    let sample_text_1 = sample_text(rows, cols, 'a');
 8557    assert_eq!(
 8558        sample_text_1,
 8559        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8560    );
 8561    let sample_text_2 = sample_text(rows, cols, 'l');
 8562    assert_eq!(
 8563        sample_text_2,
 8564        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8565    );
 8566    let sample_text_3 = sample_text(rows, cols, 'v');
 8567    assert_eq!(
 8568        sample_text_3,
 8569        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8570    );
 8571
 8572    let fs = FakeFs::new(cx.executor());
 8573    fs.insert_tree(
 8574        path!("/a"),
 8575        json!({
 8576            "main.rs": sample_text_1,
 8577            "other.rs": sample_text_2,
 8578            "lib.rs": sample_text_3,
 8579        }),
 8580    )
 8581    .await;
 8582
 8583    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8584    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8585    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8586
 8587    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8588    language_registry.add(rust_lang());
 8589    let mut fake_servers = language_registry.register_fake_lsp(
 8590        "Rust",
 8591        FakeLspAdapter {
 8592            capabilities: lsp::ServerCapabilities {
 8593                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8594                ..Default::default()
 8595            },
 8596            ..Default::default()
 8597        },
 8598    );
 8599
 8600    let worktree = project.update(cx, |project, cx| {
 8601        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8602        assert_eq!(worktrees.len(), 1);
 8603        worktrees.pop().unwrap()
 8604    });
 8605    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8606
 8607    let buffer_1 = project
 8608        .update(cx, |project, cx| {
 8609            project.open_buffer((worktree_id, "main.rs"), cx)
 8610        })
 8611        .await
 8612        .unwrap();
 8613    let buffer_2 = project
 8614        .update(cx, |project, cx| {
 8615            project.open_buffer((worktree_id, "other.rs"), cx)
 8616        })
 8617        .await
 8618        .unwrap();
 8619    let buffer_3 = project
 8620        .update(cx, |project, cx| {
 8621            project.open_buffer((worktree_id, "lib.rs"), cx)
 8622        })
 8623        .await
 8624        .unwrap();
 8625
 8626    let multi_buffer = cx.new(|cx| {
 8627        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8628        multi_buffer.push_excerpts(
 8629            buffer_1.clone(),
 8630            [
 8631                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8632                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8633                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8634            ],
 8635            cx,
 8636        );
 8637        multi_buffer.push_excerpts(
 8638            buffer_2.clone(),
 8639            [
 8640                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8641                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8642                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8643            ],
 8644            cx,
 8645        );
 8646        multi_buffer.push_excerpts(
 8647            buffer_3.clone(),
 8648            [
 8649                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8650                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8651                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8652            ],
 8653            cx,
 8654        );
 8655        multi_buffer
 8656    });
 8657    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8658        Editor::new(
 8659            EditorMode::full(),
 8660            multi_buffer,
 8661            Some(project.clone()),
 8662            window,
 8663            cx,
 8664        )
 8665    });
 8666
 8667    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8668        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8669            s.select_ranges(Some(1..2))
 8670        });
 8671        editor.insert("|one|two|three|", window, cx);
 8672    });
 8673    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8674    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8675        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8676            s.select_ranges(Some(60..70))
 8677        });
 8678        editor.insert("|four|five|six|", window, cx);
 8679    });
 8680    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8681
 8682    // First two buffers should be edited, but not the third one.
 8683    assert_eq!(
 8684        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8685        "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}",
 8686    );
 8687    buffer_1.update(cx, |buffer, _| {
 8688        assert!(buffer.is_dirty());
 8689        assert_eq!(
 8690            buffer.text(),
 8691            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8692        )
 8693    });
 8694    buffer_2.update(cx, |buffer, _| {
 8695        assert!(buffer.is_dirty());
 8696        assert_eq!(
 8697            buffer.text(),
 8698            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8699        )
 8700    });
 8701    buffer_3.update(cx, |buffer, _| {
 8702        assert!(!buffer.is_dirty());
 8703        assert_eq!(buffer.text(), sample_text_3,)
 8704    });
 8705    cx.executor().run_until_parked();
 8706
 8707    cx.executor().start_waiting();
 8708    let save = multi_buffer_editor
 8709        .update_in(cx, |editor, window, cx| {
 8710            editor.save(true, project.clone(), window, cx)
 8711        })
 8712        .unwrap();
 8713
 8714    let fake_server = fake_servers.next().await.unwrap();
 8715    fake_server
 8716        .server
 8717        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8718            Ok(Some(vec![lsp::TextEdit::new(
 8719                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8720                format!("[{} formatted]", params.text_document.uri),
 8721            )]))
 8722        })
 8723        .detach();
 8724    save.await;
 8725
 8726    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8727    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8728    assert_eq!(
 8729        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8730        uri!(
 8731            "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}"
 8732        ),
 8733    );
 8734    buffer_1.update(cx, |buffer, _| {
 8735        assert!(!buffer.is_dirty());
 8736        assert_eq!(
 8737            buffer.text(),
 8738            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8739        )
 8740    });
 8741    buffer_2.update(cx, |buffer, _| {
 8742        assert!(!buffer.is_dirty());
 8743        assert_eq!(
 8744            buffer.text(),
 8745            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8746        )
 8747    });
 8748    buffer_3.update(cx, |buffer, _| {
 8749        assert!(!buffer.is_dirty());
 8750        assert_eq!(buffer.text(), sample_text_3,)
 8751    });
 8752}
 8753
 8754#[gpui::test]
 8755async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8756    init_test(cx, |_| {});
 8757
 8758    let fs = FakeFs::new(cx.executor());
 8759    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8760
 8761    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8762
 8763    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8764    language_registry.add(rust_lang());
 8765    let mut fake_servers = language_registry.register_fake_lsp(
 8766        "Rust",
 8767        FakeLspAdapter {
 8768            capabilities: lsp::ServerCapabilities {
 8769                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8770                ..Default::default()
 8771            },
 8772            ..Default::default()
 8773        },
 8774    );
 8775
 8776    let buffer = project
 8777        .update(cx, |project, cx| {
 8778            project.open_local_buffer(path!("/file.rs"), cx)
 8779        })
 8780        .await
 8781        .unwrap();
 8782
 8783    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8784    let (editor, cx) = cx.add_window_view(|window, cx| {
 8785        build_editor_with_project(project.clone(), buffer, window, cx)
 8786    });
 8787    editor.update_in(cx, |editor, window, cx| {
 8788        editor.set_text("one\ntwo\nthree\n", window, cx)
 8789    });
 8790    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8791
 8792    cx.executor().start_waiting();
 8793    let fake_server = fake_servers.next().await.unwrap();
 8794
 8795    let save = editor
 8796        .update_in(cx, |editor, window, cx| {
 8797            editor.save(true, project.clone(), window, cx)
 8798        })
 8799        .unwrap();
 8800    fake_server
 8801        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8802            assert_eq!(
 8803                params.text_document.uri,
 8804                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8805            );
 8806            assert_eq!(params.options.tab_size, 4);
 8807            Ok(Some(vec![lsp::TextEdit::new(
 8808                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8809                ", ".to_string(),
 8810            )]))
 8811        })
 8812        .next()
 8813        .await;
 8814    cx.executor().start_waiting();
 8815    save.await;
 8816    assert_eq!(
 8817        editor.update(cx, |editor, cx| editor.text(cx)),
 8818        "one, two\nthree\n"
 8819    );
 8820    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8821
 8822    editor.update_in(cx, |editor, window, cx| {
 8823        editor.set_text("one\ntwo\nthree\n", window, cx)
 8824    });
 8825    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8826
 8827    // Ensure we can still save even if formatting hangs.
 8828    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8829        move |params, _| async move {
 8830            assert_eq!(
 8831                params.text_document.uri,
 8832                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8833            );
 8834            futures::future::pending::<()>().await;
 8835            unreachable!()
 8836        },
 8837    );
 8838    let save = editor
 8839        .update_in(cx, |editor, window, cx| {
 8840            editor.save(true, project.clone(), window, cx)
 8841        })
 8842        .unwrap();
 8843    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8844    cx.executor().start_waiting();
 8845    save.await;
 8846    assert_eq!(
 8847        editor.update(cx, |editor, cx| editor.text(cx)),
 8848        "one\ntwo\nthree\n"
 8849    );
 8850    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8851
 8852    // For non-dirty buffer, no formatting request should be sent
 8853    let save = editor
 8854        .update_in(cx, |editor, window, cx| {
 8855            editor.save(true, project.clone(), window, cx)
 8856        })
 8857        .unwrap();
 8858    let _pending_format_request = fake_server
 8859        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8860            panic!("Should not be invoked on non-dirty buffer");
 8861        })
 8862        .next();
 8863    cx.executor().start_waiting();
 8864    save.await;
 8865
 8866    // Set Rust language override and assert overridden tabsize is sent to language server
 8867    update_test_language_settings(cx, |settings| {
 8868        settings.languages.insert(
 8869            "Rust".into(),
 8870            LanguageSettingsContent {
 8871                tab_size: NonZeroU32::new(8),
 8872                ..Default::default()
 8873            },
 8874        );
 8875    });
 8876
 8877    editor.update_in(cx, |editor, window, cx| {
 8878        editor.set_text("somehting_new\n", window, cx)
 8879    });
 8880    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8881    let save = editor
 8882        .update_in(cx, |editor, window, cx| {
 8883            editor.save(true, project.clone(), window, cx)
 8884        })
 8885        .unwrap();
 8886    fake_server
 8887        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8888            assert_eq!(
 8889                params.text_document.uri,
 8890                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8891            );
 8892            assert_eq!(params.options.tab_size, 8);
 8893            Ok(Some(vec![]))
 8894        })
 8895        .next()
 8896        .await;
 8897    cx.executor().start_waiting();
 8898    save.await;
 8899}
 8900
 8901#[gpui::test]
 8902async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8903    init_test(cx, |settings| {
 8904        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8905            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8906        ))
 8907    });
 8908
 8909    let fs = FakeFs::new(cx.executor());
 8910    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8911
 8912    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8913
 8914    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8915    language_registry.add(Arc::new(Language::new(
 8916        LanguageConfig {
 8917            name: "Rust".into(),
 8918            matcher: LanguageMatcher {
 8919                path_suffixes: vec!["rs".to_string()],
 8920                ..Default::default()
 8921            },
 8922            ..LanguageConfig::default()
 8923        },
 8924        Some(tree_sitter_rust::LANGUAGE.into()),
 8925    )));
 8926    update_test_language_settings(cx, |settings| {
 8927        // Enable Prettier formatting for the same buffer, and ensure
 8928        // LSP is called instead of Prettier.
 8929        settings.defaults.prettier = Some(PrettierSettings {
 8930            allowed: true,
 8931            ..PrettierSettings::default()
 8932        });
 8933    });
 8934    let mut fake_servers = language_registry.register_fake_lsp(
 8935        "Rust",
 8936        FakeLspAdapter {
 8937            capabilities: lsp::ServerCapabilities {
 8938                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8939                ..Default::default()
 8940            },
 8941            ..Default::default()
 8942        },
 8943    );
 8944
 8945    let buffer = project
 8946        .update(cx, |project, cx| {
 8947            project.open_local_buffer(path!("/file.rs"), cx)
 8948        })
 8949        .await
 8950        .unwrap();
 8951
 8952    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8953    let (editor, cx) = cx.add_window_view(|window, cx| {
 8954        build_editor_with_project(project.clone(), buffer, window, cx)
 8955    });
 8956    editor.update_in(cx, |editor, window, cx| {
 8957        editor.set_text("one\ntwo\nthree\n", window, cx)
 8958    });
 8959
 8960    cx.executor().start_waiting();
 8961    let fake_server = fake_servers.next().await.unwrap();
 8962
 8963    let format = editor
 8964        .update_in(cx, |editor, window, cx| {
 8965            editor.perform_format(
 8966                project.clone(),
 8967                FormatTrigger::Manual,
 8968                FormatTarget::Buffers,
 8969                window,
 8970                cx,
 8971            )
 8972        })
 8973        .unwrap();
 8974    fake_server
 8975        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8976            assert_eq!(
 8977                params.text_document.uri,
 8978                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8979            );
 8980            assert_eq!(params.options.tab_size, 4);
 8981            Ok(Some(vec![lsp::TextEdit::new(
 8982                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8983                ", ".to_string(),
 8984            )]))
 8985        })
 8986        .next()
 8987        .await;
 8988    cx.executor().start_waiting();
 8989    format.await;
 8990    assert_eq!(
 8991        editor.update(cx, |editor, cx| editor.text(cx)),
 8992        "one, two\nthree\n"
 8993    );
 8994
 8995    editor.update_in(cx, |editor, window, cx| {
 8996        editor.set_text("one\ntwo\nthree\n", window, cx)
 8997    });
 8998    // Ensure we don't lock if formatting hangs.
 8999    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9000        move |params, _| async move {
 9001            assert_eq!(
 9002                params.text_document.uri,
 9003                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9004            );
 9005            futures::future::pending::<()>().await;
 9006            unreachable!()
 9007        },
 9008    );
 9009    let format = editor
 9010        .update_in(cx, |editor, window, cx| {
 9011            editor.perform_format(
 9012                project,
 9013                FormatTrigger::Manual,
 9014                FormatTarget::Buffers,
 9015                window,
 9016                cx,
 9017            )
 9018        })
 9019        .unwrap();
 9020    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9021    cx.executor().start_waiting();
 9022    format.await;
 9023    assert_eq!(
 9024        editor.update(cx, |editor, cx| editor.text(cx)),
 9025        "one\ntwo\nthree\n"
 9026    );
 9027}
 9028
 9029#[gpui::test]
 9030async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9031    init_test(cx, |settings| {
 9032        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9033        settings.defaults.formatter =
 9034            Some(language_settings::SelectedFormatter::List(FormatterList(
 9035                vec![
 9036                    Formatter::LanguageServer { name: None },
 9037                    Formatter::CodeActions(
 9038                        [
 9039                            ("code-action-1".into(), true),
 9040                            ("code-action-2".into(), true),
 9041                        ]
 9042                        .into_iter()
 9043                        .collect(),
 9044                    ),
 9045                ]
 9046                .into(),
 9047            )))
 9048    });
 9049
 9050    let fs = FakeFs::new(cx.executor());
 9051    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9052        .await;
 9053
 9054    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9055    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9056    language_registry.add(rust_lang());
 9057
 9058    let mut fake_servers = language_registry.register_fake_lsp(
 9059        "Rust",
 9060        FakeLspAdapter {
 9061            capabilities: lsp::ServerCapabilities {
 9062                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9063                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9064                    commands: vec!["the-command-for-code-action-1".into()],
 9065                    ..Default::default()
 9066                }),
 9067                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9068                ..Default::default()
 9069            },
 9070            ..Default::default()
 9071        },
 9072    );
 9073
 9074    let buffer = project
 9075        .update(cx, |project, cx| {
 9076            project.open_local_buffer(path!("/file.rs"), cx)
 9077        })
 9078        .await
 9079        .unwrap();
 9080
 9081    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9082    let (editor, cx) = cx.add_window_view(|window, cx| {
 9083        build_editor_with_project(project.clone(), buffer, window, cx)
 9084    });
 9085
 9086    cx.executor().start_waiting();
 9087
 9088    let fake_server = fake_servers.next().await.unwrap();
 9089    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9090        move |_params, _| async move {
 9091            Ok(Some(vec![lsp::TextEdit::new(
 9092                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9093                "applied-formatting\n".to_string(),
 9094            )]))
 9095        },
 9096    );
 9097    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9098        move |params, _| async move {
 9099            assert_eq!(
 9100                params.context.only,
 9101                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9102            );
 9103            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9104            Ok(Some(vec![
 9105                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9106                    kind: Some("code-action-1".into()),
 9107                    edit: Some(lsp::WorkspaceEdit::new(
 9108                        [(
 9109                            uri.clone(),
 9110                            vec![lsp::TextEdit::new(
 9111                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9112                                "applied-code-action-1-edit\n".to_string(),
 9113                            )],
 9114                        )]
 9115                        .into_iter()
 9116                        .collect(),
 9117                    )),
 9118                    command: Some(lsp::Command {
 9119                        command: "the-command-for-code-action-1".into(),
 9120                        ..Default::default()
 9121                    }),
 9122                    ..Default::default()
 9123                }),
 9124                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9125                    kind: Some("code-action-2".into()),
 9126                    edit: Some(lsp::WorkspaceEdit::new(
 9127                        [(
 9128                            uri.clone(),
 9129                            vec![lsp::TextEdit::new(
 9130                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9131                                "applied-code-action-2-edit\n".to_string(),
 9132                            )],
 9133                        )]
 9134                        .into_iter()
 9135                        .collect(),
 9136                    )),
 9137                    ..Default::default()
 9138                }),
 9139            ]))
 9140        },
 9141    );
 9142
 9143    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9144        move |params, _| async move { Ok(params) }
 9145    });
 9146
 9147    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9148    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9149        let fake = fake_server.clone();
 9150        let lock = command_lock.clone();
 9151        move |params, _| {
 9152            assert_eq!(params.command, "the-command-for-code-action-1");
 9153            let fake = fake.clone();
 9154            let lock = lock.clone();
 9155            async move {
 9156                lock.lock().await;
 9157                fake.server
 9158                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9159                        label: None,
 9160                        edit: lsp::WorkspaceEdit {
 9161                            changes: Some(
 9162                                [(
 9163                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9164                                    vec![lsp::TextEdit {
 9165                                        range: lsp::Range::new(
 9166                                            lsp::Position::new(0, 0),
 9167                                            lsp::Position::new(0, 0),
 9168                                        ),
 9169                                        new_text: "applied-code-action-1-command\n".into(),
 9170                                    }],
 9171                                )]
 9172                                .into_iter()
 9173                                .collect(),
 9174                            ),
 9175                            ..Default::default()
 9176                        },
 9177                    })
 9178                    .await
 9179                    .into_response()
 9180                    .unwrap();
 9181                Ok(Some(json!(null)))
 9182            }
 9183        }
 9184    });
 9185
 9186    cx.executor().start_waiting();
 9187    editor
 9188        .update_in(cx, |editor, window, cx| {
 9189            editor.perform_format(
 9190                project.clone(),
 9191                FormatTrigger::Manual,
 9192                FormatTarget::Buffers,
 9193                window,
 9194                cx,
 9195            )
 9196        })
 9197        .unwrap()
 9198        .await;
 9199    editor.update(cx, |editor, cx| {
 9200        assert_eq!(
 9201            editor.text(cx),
 9202            r#"
 9203                applied-code-action-2-edit
 9204                applied-code-action-1-command
 9205                applied-code-action-1-edit
 9206                applied-formatting
 9207                one
 9208                two
 9209                three
 9210            "#
 9211            .unindent()
 9212        );
 9213    });
 9214
 9215    editor.update_in(cx, |editor, window, cx| {
 9216        editor.undo(&Default::default(), window, cx);
 9217        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9218    });
 9219
 9220    // Perform a manual edit while waiting for an LSP command
 9221    // that's being run as part of a formatting code action.
 9222    let lock_guard = command_lock.lock().await;
 9223    let format = editor
 9224        .update_in(cx, |editor, window, cx| {
 9225            editor.perform_format(
 9226                project.clone(),
 9227                FormatTrigger::Manual,
 9228                FormatTarget::Buffers,
 9229                window,
 9230                cx,
 9231            )
 9232        })
 9233        .unwrap();
 9234    cx.run_until_parked();
 9235    editor.update(cx, |editor, cx| {
 9236        assert_eq!(
 9237            editor.text(cx),
 9238            r#"
 9239                applied-code-action-1-edit
 9240                applied-formatting
 9241                one
 9242                two
 9243                three
 9244            "#
 9245            .unindent()
 9246        );
 9247
 9248        editor.buffer.update(cx, |buffer, cx| {
 9249            let ix = buffer.len(cx);
 9250            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9251        });
 9252    });
 9253
 9254    // Allow the LSP command to proceed. Because the buffer was edited,
 9255    // the second code action will not be run.
 9256    drop(lock_guard);
 9257    format.await;
 9258    editor.update_in(cx, |editor, window, cx| {
 9259        assert_eq!(
 9260            editor.text(cx),
 9261            r#"
 9262                applied-code-action-1-command
 9263                applied-code-action-1-edit
 9264                applied-formatting
 9265                one
 9266                two
 9267                three
 9268                edited
 9269            "#
 9270            .unindent()
 9271        );
 9272
 9273        // The manual edit is undone first, because it is the last thing the user did
 9274        // (even though the command completed afterwards).
 9275        editor.undo(&Default::default(), window, cx);
 9276        assert_eq!(
 9277            editor.text(cx),
 9278            r#"
 9279                applied-code-action-1-command
 9280                applied-code-action-1-edit
 9281                applied-formatting
 9282                one
 9283                two
 9284                three
 9285            "#
 9286            .unindent()
 9287        );
 9288
 9289        // All the formatting (including the command, which completed after the manual edit)
 9290        // is undone together.
 9291        editor.undo(&Default::default(), window, cx);
 9292        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9293    });
 9294}
 9295
 9296#[gpui::test]
 9297async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9298    init_test(cx, |settings| {
 9299        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9300            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9301        ))
 9302    });
 9303
 9304    let fs = FakeFs::new(cx.executor());
 9305    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9306
 9307    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9308
 9309    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9310    language_registry.add(Arc::new(Language::new(
 9311        LanguageConfig {
 9312            name: "TypeScript".into(),
 9313            matcher: LanguageMatcher {
 9314                path_suffixes: vec!["ts".to_string()],
 9315                ..Default::default()
 9316            },
 9317            ..LanguageConfig::default()
 9318        },
 9319        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9320    )));
 9321    update_test_language_settings(cx, |settings| {
 9322        settings.defaults.prettier = Some(PrettierSettings {
 9323            allowed: true,
 9324            ..PrettierSettings::default()
 9325        });
 9326    });
 9327    let mut fake_servers = language_registry.register_fake_lsp(
 9328        "TypeScript",
 9329        FakeLspAdapter {
 9330            capabilities: lsp::ServerCapabilities {
 9331                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9332                ..Default::default()
 9333            },
 9334            ..Default::default()
 9335        },
 9336    );
 9337
 9338    let buffer = project
 9339        .update(cx, |project, cx| {
 9340            project.open_local_buffer(path!("/file.ts"), cx)
 9341        })
 9342        .await
 9343        .unwrap();
 9344
 9345    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9346    let (editor, cx) = cx.add_window_view(|window, cx| {
 9347        build_editor_with_project(project.clone(), buffer, window, cx)
 9348    });
 9349    editor.update_in(cx, |editor, window, cx| {
 9350        editor.set_text(
 9351            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9352            window,
 9353            cx,
 9354        )
 9355    });
 9356
 9357    cx.executor().start_waiting();
 9358    let fake_server = fake_servers.next().await.unwrap();
 9359
 9360    let format = editor
 9361        .update_in(cx, |editor, window, cx| {
 9362            editor.perform_code_action_kind(
 9363                project.clone(),
 9364                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9365                window,
 9366                cx,
 9367            )
 9368        })
 9369        .unwrap();
 9370    fake_server
 9371        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9372            assert_eq!(
 9373                params.text_document.uri,
 9374                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9375            );
 9376            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9377                lsp::CodeAction {
 9378                    title: "Organize Imports".to_string(),
 9379                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9380                    edit: Some(lsp::WorkspaceEdit {
 9381                        changes: Some(
 9382                            [(
 9383                                params.text_document.uri.clone(),
 9384                                vec![lsp::TextEdit::new(
 9385                                    lsp::Range::new(
 9386                                        lsp::Position::new(1, 0),
 9387                                        lsp::Position::new(2, 0),
 9388                                    ),
 9389                                    "".to_string(),
 9390                                )],
 9391                            )]
 9392                            .into_iter()
 9393                            .collect(),
 9394                        ),
 9395                        ..Default::default()
 9396                    }),
 9397                    ..Default::default()
 9398                },
 9399            )]))
 9400        })
 9401        .next()
 9402        .await;
 9403    cx.executor().start_waiting();
 9404    format.await;
 9405    assert_eq!(
 9406        editor.update(cx, |editor, cx| editor.text(cx)),
 9407        "import { a } from 'module';\n\nconst x = a;\n"
 9408    );
 9409
 9410    editor.update_in(cx, |editor, window, cx| {
 9411        editor.set_text(
 9412            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9413            window,
 9414            cx,
 9415        )
 9416    });
 9417    // Ensure we don't lock if code action hangs.
 9418    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9419        move |params, _| async move {
 9420            assert_eq!(
 9421                params.text_document.uri,
 9422                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9423            );
 9424            futures::future::pending::<()>().await;
 9425            unreachable!()
 9426        },
 9427    );
 9428    let format = editor
 9429        .update_in(cx, |editor, window, cx| {
 9430            editor.perform_code_action_kind(
 9431                project,
 9432                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9433                window,
 9434                cx,
 9435            )
 9436        })
 9437        .unwrap();
 9438    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9439    cx.executor().start_waiting();
 9440    format.await;
 9441    assert_eq!(
 9442        editor.update(cx, |editor, cx| editor.text(cx)),
 9443        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9444    );
 9445}
 9446
 9447#[gpui::test]
 9448async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9449    init_test(cx, |_| {});
 9450
 9451    let mut cx = EditorLspTestContext::new_rust(
 9452        lsp::ServerCapabilities {
 9453            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9454            ..Default::default()
 9455        },
 9456        cx,
 9457    )
 9458    .await;
 9459
 9460    cx.set_state(indoc! {"
 9461        one.twoˇ
 9462    "});
 9463
 9464    // The format request takes a long time. When it completes, it inserts
 9465    // a newline and an indent before the `.`
 9466    cx.lsp
 9467        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9468            let executor = cx.background_executor().clone();
 9469            async move {
 9470                executor.timer(Duration::from_millis(100)).await;
 9471                Ok(Some(vec![lsp::TextEdit {
 9472                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9473                    new_text: "\n    ".into(),
 9474                }]))
 9475            }
 9476        });
 9477
 9478    // Submit a format request.
 9479    let format_1 = cx
 9480        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9481        .unwrap();
 9482    cx.executor().run_until_parked();
 9483
 9484    // Submit a second format request.
 9485    let format_2 = cx
 9486        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9487        .unwrap();
 9488    cx.executor().run_until_parked();
 9489
 9490    // Wait for both format requests to complete
 9491    cx.executor().advance_clock(Duration::from_millis(200));
 9492    cx.executor().start_waiting();
 9493    format_1.await.unwrap();
 9494    cx.executor().start_waiting();
 9495    format_2.await.unwrap();
 9496
 9497    // The formatting edits only happens once.
 9498    cx.assert_editor_state(indoc! {"
 9499        one
 9500            .twoˇ
 9501    "});
 9502}
 9503
 9504#[gpui::test]
 9505async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9506    init_test(cx, |settings| {
 9507        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9508    });
 9509
 9510    let mut cx = EditorLspTestContext::new_rust(
 9511        lsp::ServerCapabilities {
 9512            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9513            ..Default::default()
 9514        },
 9515        cx,
 9516    )
 9517    .await;
 9518
 9519    // Set up a buffer white some trailing whitespace and no trailing newline.
 9520    cx.set_state(
 9521        &[
 9522            "one ",   //
 9523            "twoˇ",   //
 9524            "three ", //
 9525            "four",   //
 9526        ]
 9527        .join("\n"),
 9528    );
 9529
 9530    // Submit a format request.
 9531    let format = cx
 9532        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9533        .unwrap();
 9534
 9535    // Record which buffer changes have been sent to the language server
 9536    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9537    cx.lsp
 9538        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9539            let buffer_changes = buffer_changes.clone();
 9540            move |params, _| {
 9541                buffer_changes.lock().extend(
 9542                    params
 9543                        .content_changes
 9544                        .into_iter()
 9545                        .map(|e| (e.range.unwrap(), e.text)),
 9546                );
 9547            }
 9548        });
 9549
 9550    // Handle formatting requests to the language server.
 9551    cx.lsp
 9552        .set_request_handler::<lsp::request::Formatting, _, _>({
 9553            let buffer_changes = buffer_changes.clone();
 9554            move |_, _| {
 9555                // When formatting is requested, trailing whitespace has already been stripped,
 9556                // and the trailing newline has already been added.
 9557                assert_eq!(
 9558                    &buffer_changes.lock()[1..],
 9559                    &[
 9560                        (
 9561                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9562                            "".into()
 9563                        ),
 9564                        (
 9565                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9566                            "".into()
 9567                        ),
 9568                        (
 9569                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9570                            "\n".into()
 9571                        ),
 9572                    ]
 9573                );
 9574
 9575                // Insert blank lines between each line of the buffer.
 9576                async move {
 9577                    Ok(Some(vec![
 9578                        lsp::TextEdit {
 9579                            range: lsp::Range::new(
 9580                                lsp::Position::new(1, 0),
 9581                                lsp::Position::new(1, 0),
 9582                            ),
 9583                            new_text: "\n".into(),
 9584                        },
 9585                        lsp::TextEdit {
 9586                            range: lsp::Range::new(
 9587                                lsp::Position::new(2, 0),
 9588                                lsp::Position::new(2, 0),
 9589                            ),
 9590                            new_text: "\n".into(),
 9591                        },
 9592                    ]))
 9593                }
 9594            }
 9595        });
 9596
 9597    // After formatting the buffer, the trailing whitespace is stripped,
 9598    // a newline is appended, and the edits provided by the language server
 9599    // have been applied.
 9600    format.await.unwrap();
 9601    cx.assert_editor_state(
 9602        &[
 9603            "one",   //
 9604            "",      //
 9605            "twoˇ",  //
 9606            "",      //
 9607            "three", //
 9608            "four",  //
 9609            "",      //
 9610        ]
 9611        .join("\n"),
 9612    );
 9613
 9614    // Undoing the formatting undoes the trailing whitespace removal, the
 9615    // trailing newline, and the LSP edits.
 9616    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9617    cx.assert_editor_state(
 9618        &[
 9619            "one ",   //
 9620            "twoˇ",   //
 9621            "three ", //
 9622            "four",   //
 9623        ]
 9624        .join("\n"),
 9625    );
 9626}
 9627
 9628#[gpui::test]
 9629async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9630    cx: &mut TestAppContext,
 9631) {
 9632    init_test(cx, |_| {});
 9633
 9634    cx.update(|cx| {
 9635        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9636            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9637                settings.auto_signature_help = Some(true);
 9638            });
 9639        });
 9640    });
 9641
 9642    let mut cx = EditorLspTestContext::new_rust(
 9643        lsp::ServerCapabilities {
 9644            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9645                ..Default::default()
 9646            }),
 9647            ..Default::default()
 9648        },
 9649        cx,
 9650    )
 9651    .await;
 9652
 9653    let language = Language::new(
 9654        LanguageConfig {
 9655            name: "Rust".into(),
 9656            brackets: BracketPairConfig {
 9657                pairs: vec![
 9658                    BracketPair {
 9659                        start: "{".to_string(),
 9660                        end: "}".to_string(),
 9661                        close: true,
 9662                        surround: true,
 9663                        newline: true,
 9664                    },
 9665                    BracketPair {
 9666                        start: "(".to_string(),
 9667                        end: ")".to_string(),
 9668                        close: true,
 9669                        surround: true,
 9670                        newline: true,
 9671                    },
 9672                    BracketPair {
 9673                        start: "/*".to_string(),
 9674                        end: " */".to_string(),
 9675                        close: true,
 9676                        surround: true,
 9677                        newline: true,
 9678                    },
 9679                    BracketPair {
 9680                        start: "[".to_string(),
 9681                        end: "]".to_string(),
 9682                        close: false,
 9683                        surround: false,
 9684                        newline: true,
 9685                    },
 9686                    BracketPair {
 9687                        start: "\"".to_string(),
 9688                        end: "\"".to_string(),
 9689                        close: true,
 9690                        surround: true,
 9691                        newline: false,
 9692                    },
 9693                    BracketPair {
 9694                        start: "<".to_string(),
 9695                        end: ">".to_string(),
 9696                        close: false,
 9697                        surround: true,
 9698                        newline: true,
 9699                    },
 9700                ],
 9701                ..Default::default()
 9702            },
 9703            autoclose_before: "})]".to_string(),
 9704            ..Default::default()
 9705        },
 9706        Some(tree_sitter_rust::LANGUAGE.into()),
 9707    );
 9708    let language = Arc::new(language);
 9709
 9710    cx.language_registry().add(language.clone());
 9711    cx.update_buffer(|buffer, cx| {
 9712        buffer.set_language(Some(language), cx);
 9713    });
 9714
 9715    cx.set_state(
 9716        &r#"
 9717            fn main() {
 9718                sampleˇ
 9719            }
 9720        "#
 9721        .unindent(),
 9722    );
 9723
 9724    cx.update_editor(|editor, window, cx| {
 9725        editor.handle_input("(", window, cx);
 9726    });
 9727    cx.assert_editor_state(
 9728        &"
 9729            fn main() {
 9730                sample(ˇ)
 9731            }
 9732        "
 9733        .unindent(),
 9734    );
 9735
 9736    let mocked_response = lsp::SignatureHelp {
 9737        signatures: vec![lsp::SignatureInformation {
 9738            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9739            documentation: None,
 9740            parameters: Some(vec![
 9741                lsp::ParameterInformation {
 9742                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9743                    documentation: None,
 9744                },
 9745                lsp::ParameterInformation {
 9746                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9747                    documentation: None,
 9748                },
 9749            ]),
 9750            active_parameter: None,
 9751        }],
 9752        active_signature: Some(0),
 9753        active_parameter: Some(0),
 9754    };
 9755    handle_signature_help_request(&mut cx, mocked_response).await;
 9756
 9757    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9758        .await;
 9759
 9760    cx.editor(|editor, _, _| {
 9761        let signature_help_state = editor.signature_help_state.popover().cloned();
 9762        assert_eq!(
 9763            signature_help_state.unwrap().label,
 9764            "param1: u8, param2: u8"
 9765        );
 9766    });
 9767}
 9768
 9769#[gpui::test]
 9770async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9771    init_test(cx, |_| {});
 9772
 9773    cx.update(|cx| {
 9774        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9775            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9776                settings.auto_signature_help = Some(false);
 9777                settings.show_signature_help_after_edits = Some(false);
 9778            });
 9779        });
 9780    });
 9781
 9782    let mut cx = EditorLspTestContext::new_rust(
 9783        lsp::ServerCapabilities {
 9784            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9785                ..Default::default()
 9786            }),
 9787            ..Default::default()
 9788        },
 9789        cx,
 9790    )
 9791    .await;
 9792
 9793    let language = Language::new(
 9794        LanguageConfig {
 9795            name: "Rust".into(),
 9796            brackets: BracketPairConfig {
 9797                pairs: vec![
 9798                    BracketPair {
 9799                        start: "{".to_string(),
 9800                        end: "}".to_string(),
 9801                        close: true,
 9802                        surround: true,
 9803                        newline: true,
 9804                    },
 9805                    BracketPair {
 9806                        start: "(".to_string(),
 9807                        end: ")".to_string(),
 9808                        close: true,
 9809                        surround: true,
 9810                        newline: true,
 9811                    },
 9812                    BracketPair {
 9813                        start: "/*".to_string(),
 9814                        end: " */".to_string(),
 9815                        close: true,
 9816                        surround: true,
 9817                        newline: true,
 9818                    },
 9819                    BracketPair {
 9820                        start: "[".to_string(),
 9821                        end: "]".to_string(),
 9822                        close: false,
 9823                        surround: false,
 9824                        newline: true,
 9825                    },
 9826                    BracketPair {
 9827                        start: "\"".to_string(),
 9828                        end: "\"".to_string(),
 9829                        close: true,
 9830                        surround: true,
 9831                        newline: false,
 9832                    },
 9833                    BracketPair {
 9834                        start: "<".to_string(),
 9835                        end: ">".to_string(),
 9836                        close: false,
 9837                        surround: true,
 9838                        newline: true,
 9839                    },
 9840                ],
 9841                ..Default::default()
 9842            },
 9843            autoclose_before: "})]".to_string(),
 9844            ..Default::default()
 9845        },
 9846        Some(tree_sitter_rust::LANGUAGE.into()),
 9847    );
 9848    let language = Arc::new(language);
 9849
 9850    cx.language_registry().add(language.clone());
 9851    cx.update_buffer(|buffer, cx| {
 9852        buffer.set_language(Some(language), cx);
 9853    });
 9854
 9855    // Ensure that signature_help is not called when no signature help is enabled.
 9856    cx.set_state(
 9857        &r#"
 9858            fn main() {
 9859                sampleˇ
 9860            }
 9861        "#
 9862        .unindent(),
 9863    );
 9864    cx.update_editor(|editor, window, cx| {
 9865        editor.handle_input("(", window, cx);
 9866    });
 9867    cx.assert_editor_state(
 9868        &"
 9869            fn main() {
 9870                sample(ˇ)
 9871            }
 9872        "
 9873        .unindent(),
 9874    );
 9875    cx.editor(|editor, _, _| {
 9876        assert!(editor.signature_help_state.task().is_none());
 9877    });
 9878
 9879    let mocked_response = lsp::SignatureHelp {
 9880        signatures: vec![lsp::SignatureInformation {
 9881            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9882            documentation: None,
 9883            parameters: Some(vec![
 9884                lsp::ParameterInformation {
 9885                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9886                    documentation: None,
 9887                },
 9888                lsp::ParameterInformation {
 9889                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9890                    documentation: None,
 9891                },
 9892            ]),
 9893            active_parameter: None,
 9894        }],
 9895        active_signature: Some(0),
 9896        active_parameter: Some(0),
 9897    };
 9898
 9899    // Ensure that signature_help is called when enabled afte edits
 9900    cx.update(|_, cx| {
 9901        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9902            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9903                settings.auto_signature_help = Some(false);
 9904                settings.show_signature_help_after_edits = Some(true);
 9905            });
 9906        });
 9907    });
 9908    cx.set_state(
 9909        &r#"
 9910            fn main() {
 9911                sampleˇ
 9912            }
 9913        "#
 9914        .unindent(),
 9915    );
 9916    cx.update_editor(|editor, window, cx| {
 9917        editor.handle_input("(", window, cx);
 9918    });
 9919    cx.assert_editor_state(
 9920        &"
 9921            fn main() {
 9922                sample(ˇ)
 9923            }
 9924        "
 9925        .unindent(),
 9926    );
 9927    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9928    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9929        .await;
 9930    cx.update_editor(|editor, _, _| {
 9931        let signature_help_state = editor.signature_help_state.popover().cloned();
 9932        assert!(signature_help_state.is_some());
 9933        assert_eq!(
 9934            signature_help_state.unwrap().label,
 9935            "param1: u8, param2: u8"
 9936        );
 9937        editor.signature_help_state = SignatureHelpState::default();
 9938    });
 9939
 9940    // Ensure that signature_help is called when auto signature help override is enabled
 9941    cx.update(|_, cx| {
 9942        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9943            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9944                settings.auto_signature_help = Some(true);
 9945                settings.show_signature_help_after_edits = Some(false);
 9946            });
 9947        });
 9948    });
 9949    cx.set_state(
 9950        &r#"
 9951            fn main() {
 9952                sampleˇ
 9953            }
 9954        "#
 9955        .unindent(),
 9956    );
 9957    cx.update_editor(|editor, window, cx| {
 9958        editor.handle_input("(", window, cx);
 9959    });
 9960    cx.assert_editor_state(
 9961        &"
 9962            fn main() {
 9963                sample(ˇ)
 9964            }
 9965        "
 9966        .unindent(),
 9967    );
 9968    handle_signature_help_request(&mut cx, mocked_response).await;
 9969    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9970        .await;
 9971    cx.editor(|editor, _, _| {
 9972        let signature_help_state = editor.signature_help_state.popover().cloned();
 9973        assert!(signature_help_state.is_some());
 9974        assert_eq!(
 9975            signature_help_state.unwrap().label,
 9976            "param1: u8, param2: u8"
 9977        );
 9978    });
 9979}
 9980
 9981#[gpui::test]
 9982async fn test_signature_help(cx: &mut TestAppContext) {
 9983    init_test(cx, |_| {});
 9984    cx.update(|cx| {
 9985        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9986            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9987                settings.auto_signature_help = Some(true);
 9988            });
 9989        });
 9990    });
 9991
 9992    let mut cx = EditorLspTestContext::new_rust(
 9993        lsp::ServerCapabilities {
 9994            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9995                ..Default::default()
 9996            }),
 9997            ..Default::default()
 9998        },
 9999        cx,
10000    )
10001    .await;
10002
10003    // A test that directly calls `show_signature_help`
10004    cx.update_editor(|editor, window, cx| {
10005        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10006    });
10007
10008    let mocked_response = lsp::SignatureHelp {
10009        signatures: vec![lsp::SignatureInformation {
10010            label: "fn sample(param1: u8, param2: u8)".to_string(),
10011            documentation: None,
10012            parameters: Some(vec![
10013                lsp::ParameterInformation {
10014                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10015                    documentation: None,
10016                },
10017                lsp::ParameterInformation {
10018                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10019                    documentation: None,
10020                },
10021            ]),
10022            active_parameter: None,
10023        }],
10024        active_signature: Some(0),
10025        active_parameter: Some(0),
10026    };
10027    handle_signature_help_request(&mut cx, mocked_response).await;
10028
10029    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10030        .await;
10031
10032    cx.editor(|editor, _, _| {
10033        let signature_help_state = editor.signature_help_state.popover().cloned();
10034        assert!(signature_help_state.is_some());
10035        assert_eq!(
10036            signature_help_state.unwrap().label,
10037            "param1: u8, param2: u8"
10038        );
10039    });
10040
10041    // When exiting outside from inside the brackets, `signature_help` is closed.
10042    cx.set_state(indoc! {"
10043        fn main() {
10044            sample(ˇ);
10045        }
10046
10047        fn sample(param1: u8, param2: u8) {}
10048    "});
10049
10050    cx.update_editor(|editor, window, cx| {
10051        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10052    });
10053
10054    let mocked_response = lsp::SignatureHelp {
10055        signatures: Vec::new(),
10056        active_signature: None,
10057        active_parameter: None,
10058    };
10059    handle_signature_help_request(&mut cx, mocked_response).await;
10060
10061    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10062        .await;
10063
10064    cx.editor(|editor, _, _| {
10065        assert!(!editor.signature_help_state.is_shown());
10066    });
10067
10068    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10069    cx.set_state(indoc! {"
10070        fn main() {
10071            sample(ˇ);
10072        }
10073
10074        fn sample(param1: u8, param2: u8) {}
10075    "});
10076
10077    let mocked_response = lsp::SignatureHelp {
10078        signatures: vec![lsp::SignatureInformation {
10079            label: "fn sample(param1: u8, param2: u8)".to_string(),
10080            documentation: None,
10081            parameters: Some(vec![
10082                lsp::ParameterInformation {
10083                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10084                    documentation: None,
10085                },
10086                lsp::ParameterInformation {
10087                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10088                    documentation: None,
10089                },
10090            ]),
10091            active_parameter: None,
10092        }],
10093        active_signature: Some(0),
10094        active_parameter: Some(0),
10095    };
10096    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10097    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10098        .await;
10099    cx.editor(|editor, _, _| {
10100        assert!(editor.signature_help_state.is_shown());
10101    });
10102
10103    // Restore the popover with more parameter input
10104    cx.set_state(indoc! {"
10105        fn main() {
10106            sample(param1, param2ˇ);
10107        }
10108
10109        fn sample(param1: u8, param2: u8) {}
10110    "});
10111
10112    let mocked_response = lsp::SignatureHelp {
10113        signatures: vec![lsp::SignatureInformation {
10114            label: "fn sample(param1: u8, param2: u8)".to_string(),
10115            documentation: None,
10116            parameters: Some(vec![
10117                lsp::ParameterInformation {
10118                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10119                    documentation: None,
10120                },
10121                lsp::ParameterInformation {
10122                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10123                    documentation: None,
10124                },
10125            ]),
10126            active_parameter: None,
10127        }],
10128        active_signature: Some(0),
10129        active_parameter: Some(1),
10130    };
10131    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10132    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10133        .await;
10134
10135    // When selecting a range, the popover is gone.
10136    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10137    cx.update_editor(|editor, window, cx| {
10138        editor.change_selections(None, window, cx, |s| {
10139            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10140        })
10141    });
10142    cx.assert_editor_state(indoc! {"
10143        fn main() {
10144            sample(param1, «ˇparam2»);
10145        }
10146
10147        fn sample(param1: u8, param2: u8) {}
10148    "});
10149    cx.editor(|editor, _, _| {
10150        assert!(!editor.signature_help_state.is_shown());
10151    });
10152
10153    // When unselecting again, the popover is back if within the brackets.
10154    cx.update_editor(|editor, window, cx| {
10155        editor.change_selections(None, window, cx, |s| {
10156            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10157        })
10158    });
10159    cx.assert_editor_state(indoc! {"
10160        fn main() {
10161            sample(param1, ˇparam2);
10162        }
10163
10164        fn sample(param1: u8, param2: u8) {}
10165    "});
10166    handle_signature_help_request(&mut cx, mocked_response).await;
10167    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10168        .await;
10169    cx.editor(|editor, _, _| {
10170        assert!(editor.signature_help_state.is_shown());
10171    });
10172
10173    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10174    cx.update_editor(|editor, window, cx| {
10175        editor.change_selections(None, window, cx, |s| {
10176            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10177            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10178        })
10179    });
10180    cx.assert_editor_state(indoc! {"
10181        fn main() {
10182            sample(param1, ˇparam2);
10183        }
10184
10185        fn sample(param1: u8, param2: u8) {}
10186    "});
10187
10188    let mocked_response = lsp::SignatureHelp {
10189        signatures: vec![lsp::SignatureInformation {
10190            label: "fn sample(param1: u8, param2: u8)".to_string(),
10191            documentation: None,
10192            parameters: Some(vec![
10193                lsp::ParameterInformation {
10194                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10195                    documentation: None,
10196                },
10197                lsp::ParameterInformation {
10198                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10199                    documentation: None,
10200                },
10201            ]),
10202            active_parameter: None,
10203        }],
10204        active_signature: Some(0),
10205        active_parameter: Some(1),
10206    };
10207    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10208    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10209        .await;
10210    cx.update_editor(|editor, _, cx| {
10211        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10212    });
10213    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10214        .await;
10215    cx.update_editor(|editor, window, cx| {
10216        editor.change_selections(None, window, cx, |s| {
10217            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10218        })
10219    });
10220    cx.assert_editor_state(indoc! {"
10221        fn main() {
10222            sample(param1, «ˇparam2»);
10223        }
10224
10225        fn sample(param1: u8, param2: u8) {}
10226    "});
10227    cx.update_editor(|editor, window, cx| {
10228        editor.change_selections(None, window, cx, |s| {
10229            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10230        })
10231    });
10232    cx.assert_editor_state(indoc! {"
10233        fn main() {
10234            sample(param1, ˇparam2);
10235        }
10236
10237        fn sample(param1: u8, param2: u8) {}
10238    "});
10239    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10240        .await;
10241}
10242
10243#[gpui::test]
10244async fn test_completion_mode(cx: &mut TestAppContext) {
10245    init_test(cx, |_| {});
10246    let mut cx = EditorLspTestContext::new_rust(
10247        lsp::ServerCapabilities {
10248            completion_provider: Some(lsp::CompletionOptions {
10249                resolve_provider: Some(true),
10250                ..Default::default()
10251            }),
10252            ..Default::default()
10253        },
10254        cx,
10255    )
10256    .await;
10257
10258    struct Run {
10259        run_description: &'static str,
10260        initial_state: String,
10261        buffer_marked_text: String,
10262        completion_text: &'static str,
10263        expected_with_insert_mode: String,
10264        expected_with_replace_mode: String,
10265        expected_with_replace_subsequence_mode: String,
10266        expected_with_replace_suffix_mode: String,
10267    }
10268
10269    let runs = [
10270        Run {
10271            run_description: "Start of word matches completion text",
10272            initial_state: "before ediˇ after".into(),
10273            buffer_marked_text: "before <edi|> after".into(),
10274            completion_text: "editor",
10275            expected_with_insert_mode: "before editorˇ after".into(),
10276            expected_with_replace_mode: "before editorˇ after".into(),
10277            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10278            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10279        },
10280        Run {
10281            run_description: "Accept same text at the middle of the word",
10282            initial_state: "before ediˇtor after".into(),
10283            buffer_marked_text: "before <edi|tor> after".into(),
10284            completion_text: "editor",
10285            expected_with_insert_mode: "before editorˇtor after".into(),
10286            expected_with_replace_mode: "before editorˇ after".into(),
10287            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10288            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10289        },
10290        Run {
10291            run_description: "End of word matches completion text -- cursor at end",
10292            initial_state: "before torˇ after".into(),
10293            buffer_marked_text: "before <tor|> after".into(),
10294            completion_text: "editor",
10295            expected_with_insert_mode: "before editorˇ after".into(),
10296            expected_with_replace_mode: "before editorˇ after".into(),
10297            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10298            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10299        },
10300        Run {
10301            run_description: "End of word matches completion text -- cursor at start",
10302            initial_state: "before ˇtor after".into(),
10303            buffer_marked_text: "before <|tor> after".into(),
10304            completion_text: "editor",
10305            expected_with_insert_mode: "before editorˇtor after".into(),
10306            expected_with_replace_mode: "before editorˇ after".into(),
10307            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10308            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10309        },
10310        Run {
10311            run_description: "Prepend text containing whitespace",
10312            initial_state: "pˇfield: bool".into(),
10313            buffer_marked_text: "<p|field>: bool".into(),
10314            completion_text: "pub ",
10315            expected_with_insert_mode: "pub ˇfield: bool".into(),
10316            expected_with_replace_mode: "pub ˇ: bool".into(),
10317            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10318            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10319        },
10320        Run {
10321            run_description: "Add element to start of list",
10322            initial_state: "[element_ˇelement_2]".into(),
10323            buffer_marked_text: "[<element_|element_2>]".into(),
10324            completion_text: "element_1",
10325            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10326            expected_with_replace_mode: "[element_1ˇ]".into(),
10327            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10328            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10329        },
10330        Run {
10331            run_description: "Add element to start of list -- first and second elements are equal",
10332            initial_state: "[elˇelement]".into(),
10333            buffer_marked_text: "[<el|element>]".into(),
10334            completion_text: "element",
10335            expected_with_insert_mode: "[elementˇelement]".into(),
10336            expected_with_replace_mode: "[elementˇ]".into(),
10337            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10338            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10339        },
10340        Run {
10341            run_description: "Ends with matching suffix",
10342            initial_state: "SubˇError".into(),
10343            buffer_marked_text: "<Sub|Error>".into(),
10344            completion_text: "SubscriptionError",
10345            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10346            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10347            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10348            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10349        },
10350        Run {
10351            run_description: "Suffix is a subsequence -- contiguous",
10352            initial_state: "SubˇErr".into(),
10353            buffer_marked_text: "<Sub|Err>".into(),
10354            completion_text: "SubscriptionError",
10355            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10356            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10357            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10358            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10359        },
10360        Run {
10361            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10362            initial_state: "Suˇscrirr".into(),
10363            buffer_marked_text: "<Su|scrirr>".into(),
10364            completion_text: "SubscriptionError",
10365            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10366            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10367            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10368            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10369        },
10370        Run {
10371            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10372            initial_state: "foo(indˇix)".into(),
10373            buffer_marked_text: "foo(<ind|ix>)".into(),
10374            completion_text: "node_index",
10375            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10376            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10377            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10378            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10379        },
10380    ];
10381
10382    for run in runs {
10383        let run_variations = [
10384            (LspInsertMode::Insert, run.expected_with_insert_mode),
10385            (LspInsertMode::Replace, run.expected_with_replace_mode),
10386            (
10387                LspInsertMode::ReplaceSubsequence,
10388                run.expected_with_replace_subsequence_mode,
10389            ),
10390            (
10391                LspInsertMode::ReplaceSuffix,
10392                run.expected_with_replace_suffix_mode,
10393            ),
10394        ];
10395
10396        for (lsp_insert_mode, expected_text) in run_variations {
10397            eprintln!(
10398                "run = {:?}, mode = {lsp_insert_mode:.?}",
10399                run.run_description,
10400            );
10401
10402            update_test_language_settings(&mut cx, |settings| {
10403                settings.defaults.completions = Some(CompletionSettings {
10404                    lsp_insert_mode,
10405                    words: WordsCompletionMode::Disabled,
10406                    lsp: true,
10407                    lsp_fetch_timeout_ms: 0,
10408                });
10409            });
10410
10411            cx.set_state(&run.initial_state);
10412            cx.update_editor(|editor, window, cx| {
10413                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10414            });
10415
10416            let counter = Arc::new(AtomicUsize::new(0));
10417            handle_completion_request_with_insert_and_replace(
10418                &mut cx,
10419                &run.buffer_marked_text,
10420                vec![run.completion_text],
10421                counter.clone(),
10422            )
10423            .await;
10424            cx.condition(|editor, _| editor.context_menu_visible())
10425                .await;
10426            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10427
10428            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10429                editor
10430                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10431                    .unwrap()
10432            });
10433            cx.assert_editor_state(&expected_text);
10434            handle_resolve_completion_request(&mut cx, None).await;
10435            apply_additional_edits.await.unwrap();
10436        }
10437    }
10438}
10439
10440#[gpui::test]
10441async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10442    init_test(cx, |_| {});
10443    let mut cx = EditorLspTestContext::new_rust(
10444        lsp::ServerCapabilities {
10445            completion_provider: Some(lsp::CompletionOptions {
10446                resolve_provider: Some(true),
10447                ..Default::default()
10448            }),
10449            ..Default::default()
10450        },
10451        cx,
10452    )
10453    .await;
10454
10455    let initial_state = "SubˇError";
10456    let buffer_marked_text = "<Sub|Error>";
10457    let completion_text = "SubscriptionError";
10458    let expected_with_insert_mode = "SubscriptionErrorˇError";
10459    let expected_with_replace_mode = "SubscriptionErrorˇ";
10460
10461    update_test_language_settings(&mut cx, |settings| {
10462        settings.defaults.completions = Some(CompletionSettings {
10463            words: WordsCompletionMode::Disabled,
10464            // set the opposite here to ensure that the action is overriding the default behavior
10465            lsp_insert_mode: LspInsertMode::Insert,
10466            lsp: true,
10467            lsp_fetch_timeout_ms: 0,
10468        });
10469    });
10470
10471    cx.set_state(initial_state);
10472    cx.update_editor(|editor, window, cx| {
10473        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10474    });
10475
10476    let counter = Arc::new(AtomicUsize::new(0));
10477    handle_completion_request_with_insert_and_replace(
10478        &mut cx,
10479        &buffer_marked_text,
10480        vec![completion_text],
10481        counter.clone(),
10482    )
10483    .await;
10484    cx.condition(|editor, _| editor.context_menu_visible())
10485        .await;
10486    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10487
10488    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10489        editor
10490            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10491            .unwrap()
10492    });
10493    cx.assert_editor_state(&expected_with_replace_mode);
10494    handle_resolve_completion_request(&mut cx, None).await;
10495    apply_additional_edits.await.unwrap();
10496
10497    update_test_language_settings(&mut cx, |settings| {
10498        settings.defaults.completions = Some(CompletionSettings {
10499            words: WordsCompletionMode::Disabled,
10500            // set the opposite here to ensure that the action is overriding the default behavior
10501            lsp_insert_mode: LspInsertMode::Replace,
10502            lsp: true,
10503            lsp_fetch_timeout_ms: 0,
10504        });
10505    });
10506
10507    cx.set_state(initial_state);
10508    cx.update_editor(|editor, window, cx| {
10509        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10510    });
10511    handle_completion_request_with_insert_and_replace(
10512        &mut cx,
10513        &buffer_marked_text,
10514        vec![completion_text],
10515        counter.clone(),
10516    )
10517    .await;
10518    cx.condition(|editor, _| editor.context_menu_visible())
10519        .await;
10520    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10521
10522    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10523        editor
10524            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10525            .unwrap()
10526    });
10527    cx.assert_editor_state(&expected_with_insert_mode);
10528    handle_resolve_completion_request(&mut cx, None).await;
10529    apply_additional_edits.await.unwrap();
10530}
10531
10532#[gpui::test]
10533async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10534    init_test(cx, |_| {});
10535    let mut cx = EditorLspTestContext::new_rust(
10536        lsp::ServerCapabilities {
10537            completion_provider: Some(lsp::CompletionOptions {
10538                resolve_provider: Some(true),
10539                ..Default::default()
10540            }),
10541            ..Default::default()
10542        },
10543        cx,
10544    )
10545    .await;
10546
10547    // scenario: surrounding text matches completion text
10548    let completion_text = "to_offset";
10549    let initial_state = indoc! {"
10550        1. buf.to_offˇsuffix
10551        2. buf.to_offˇsuf
10552        3. buf.to_offˇfix
10553        4. buf.to_offˇ
10554        5. into_offˇensive
10555        6. ˇsuffix
10556        7. let ˇ //
10557        8. aaˇzz
10558        9. buf.to_off«zzzzzˇ»suffix
10559        10. buf.«ˇzzzzz»suffix
10560        11. to_off«ˇzzzzz»
10561
10562        buf.to_offˇsuffix  // newest cursor
10563    "};
10564    let completion_marked_buffer = indoc! {"
10565        1. buf.to_offsuffix
10566        2. buf.to_offsuf
10567        3. buf.to_offfix
10568        4. buf.to_off
10569        5. into_offensive
10570        6. suffix
10571        7. let  //
10572        8. aazz
10573        9. buf.to_offzzzzzsuffix
10574        10. buf.zzzzzsuffix
10575        11. to_offzzzzz
10576
10577        buf.<to_off|suffix>  // newest cursor
10578    "};
10579    let expected = indoc! {"
10580        1. buf.to_offsetˇ
10581        2. buf.to_offsetˇsuf
10582        3. buf.to_offsetˇfix
10583        4. buf.to_offsetˇ
10584        5. into_offsetˇensive
10585        6. to_offsetˇsuffix
10586        7. let to_offsetˇ //
10587        8. aato_offsetˇzz
10588        9. buf.to_offsetˇ
10589        10. buf.to_offsetˇsuffix
10590        11. to_offsetˇ
10591
10592        buf.to_offsetˇ  // newest cursor
10593    "};
10594    cx.set_state(initial_state);
10595    cx.update_editor(|editor, window, cx| {
10596        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10597    });
10598    handle_completion_request_with_insert_and_replace(
10599        &mut cx,
10600        completion_marked_buffer,
10601        vec![completion_text],
10602        Arc::new(AtomicUsize::new(0)),
10603    )
10604    .await;
10605    cx.condition(|editor, _| editor.context_menu_visible())
10606        .await;
10607    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10608        editor
10609            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10610            .unwrap()
10611    });
10612    cx.assert_editor_state(expected);
10613    handle_resolve_completion_request(&mut cx, None).await;
10614    apply_additional_edits.await.unwrap();
10615
10616    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10617    let completion_text = "foo_and_bar";
10618    let initial_state = indoc! {"
10619        1. ooanbˇ
10620        2. zooanbˇ
10621        3. ooanbˇz
10622        4. zooanbˇz
10623        5. ooanˇ
10624        6. oanbˇ
10625
10626        ooanbˇ
10627    "};
10628    let completion_marked_buffer = indoc! {"
10629        1. ooanb
10630        2. zooanb
10631        3. ooanbz
10632        4. zooanbz
10633        5. ooan
10634        6. oanb
10635
10636        <ooanb|>
10637    "};
10638    let expected = indoc! {"
10639        1. foo_and_barˇ
10640        2. zfoo_and_barˇ
10641        3. foo_and_barˇz
10642        4. zfoo_and_barˇz
10643        5. ooanfoo_and_barˇ
10644        6. oanbfoo_and_barˇ
10645
10646        foo_and_barˇ
10647    "};
10648    cx.set_state(initial_state);
10649    cx.update_editor(|editor, window, cx| {
10650        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10651    });
10652    handle_completion_request_with_insert_and_replace(
10653        &mut cx,
10654        completion_marked_buffer,
10655        vec![completion_text],
10656        Arc::new(AtomicUsize::new(0)),
10657    )
10658    .await;
10659    cx.condition(|editor, _| editor.context_menu_visible())
10660        .await;
10661    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10662        editor
10663            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10664            .unwrap()
10665    });
10666    cx.assert_editor_state(expected);
10667    handle_resolve_completion_request(&mut cx, None).await;
10668    apply_additional_edits.await.unwrap();
10669
10670    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10671    // (expects the same as if it was inserted at the end)
10672    let completion_text = "foo_and_bar";
10673    let initial_state = indoc! {"
10674        1. ooˇanb
10675        2. zooˇanb
10676        3. ooˇanbz
10677        4. zooˇanbz
10678
10679        ooˇanb
10680    "};
10681    let completion_marked_buffer = indoc! {"
10682        1. ooanb
10683        2. zooanb
10684        3. ooanbz
10685        4. zooanbz
10686
10687        <oo|anb>
10688    "};
10689    let expected = indoc! {"
10690        1. foo_and_barˇ
10691        2. zfoo_and_barˇ
10692        3. foo_and_barˇz
10693        4. zfoo_and_barˇz
10694
10695        foo_and_barˇ
10696    "};
10697    cx.set_state(initial_state);
10698    cx.update_editor(|editor, window, cx| {
10699        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10700    });
10701    handle_completion_request_with_insert_and_replace(
10702        &mut cx,
10703        completion_marked_buffer,
10704        vec![completion_text],
10705        Arc::new(AtomicUsize::new(0)),
10706    )
10707    .await;
10708    cx.condition(|editor, _| editor.context_menu_visible())
10709        .await;
10710    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10711        editor
10712            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10713            .unwrap()
10714    });
10715    cx.assert_editor_state(expected);
10716    handle_resolve_completion_request(&mut cx, None).await;
10717    apply_additional_edits.await.unwrap();
10718}
10719
10720// This used to crash
10721#[gpui::test]
10722async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10723    init_test(cx, |_| {});
10724
10725    let buffer_text = indoc! {"
10726        fn main() {
10727            10.satu;
10728
10729            //
10730            // separate cursors so they open in different excerpts (manually reproducible)
10731            //
10732
10733            10.satu20;
10734        }
10735    "};
10736    let multibuffer_text_with_selections = indoc! {"
10737        fn main() {
10738            10.satuˇ;
10739
10740            //
10741
10742            //
10743
10744            10.satuˇ20;
10745        }
10746    "};
10747    let expected_multibuffer = indoc! {"
10748        fn main() {
10749            10.saturating_sub()ˇ;
10750
10751            //
10752
10753            //
10754
10755            10.saturating_sub()ˇ;
10756        }
10757    "};
10758
10759    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10760    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10761
10762    let fs = FakeFs::new(cx.executor());
10763    fs.insert_tree(
10764        path!("/a"),
10765        json!({
10766            "main.rs": buffer_text,
10767        }),
10768    )
10769    .await;
10770
10771    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10772    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10773    language_registry.add(rust_lang());
10774    let mut fake_servers = language_registry.register_fake_lsp(
10775        "Rust",
10776        FakeLspAdapter {
10777            capabilities: lsp::ServerCapabilities {
10778                completion_provider: Some(lsp::CompletionOptions {
10779                    resolve_provider: None,
10780                    ..lsp::CompletionOptions::default()
10781                }),
10782                ..lsp::ServerCapabilities::default()
10783            },
10784            ..FakeLspAdapter::default()
10785        },
10786    );
10787    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10788    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10789    let buffer = project
10790        .update(cx, |project, cx| {
10791            project.open_local_buffer(path!("/a/main.rs"), cx)
10792        })
10793        .await
10794        .unwrap();
10795
10796    let multi_buffer = cx.new(|cx| {
10797        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10798        multi_buffer.push_excerpts(
10799            buffer.clone(),
10800            [ExcerptRange::new(0..first_excerpt_end)],
10801            cx,
10802        );
10803        multi_buffer.push_excerpts(
10804            buffer.clone(),
10805            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10806            cx,
10807        );
10808        multi_buffer
10809    });
10810
10811    let editor = workspace
10812        .update(cx, |_, window, cx| {
10813            cx.new(|cx| {
10814                Editor::new(
10815                    EditorMode::Full {
10816                        scale_ui_elements_with_buffer_font_size: false,
10817                        show_active_line_background: false,
10818                        sized_by_content: false,
10819                    },
10820                    multi_buffer.clone(),
10821                    Some(project.clone()),
10822                    window,
10823                    cx,
10824                )
10825            })
10826        })
10827        .unwrap();
10828
10829    let pane = workspace
10830        .update(cx, |workspace, _, _| workspace.active_pane().clone())
10831        .unwrap();
10832    pane.update_in(cx, |pane, window, cx| {
10833        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10834    });
10835
10836    let fake_server = fake_servers.next().await.unwrap();
10837
10838    editor.update_in(cx, |editor, window, cx| {
10839        editor.change_selections(None, window, cx, |s| {
10840            s.select_ranges([
10841                Point::new(1, 11)..Point::new(1, 11),
10842                Point::new(7, 11)..Point::new(7, 11),
10843            ])
10844        });
10845
10846        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10847    });
10848
10849    editor.update_in(cx, |editor, window, cx| {
10850        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10851    });
10852
10853    fake_server
10854        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10855            let completion_item = lsp::CompletionItem {
10856                label: "saturating_sub()".into(),
10857                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10858                    lsp::InsertReplaceEdit {
10859                        new_text: "saturating_sub()".to_owned(),
10860                        insert: lsp::Range::new(
10861                            lsp::Position::new(7, 7),
10862                            lsp::Position::new(7, 11),
10863                        ),
10864                        replace: lsp::Range::new(
10865                            lsp::Position::new(7, 7),
10866                            lsp::Position::new(7, 13),
10867                        ),
10868                    },
10869                )),
10870                ..lsp::CompletionItem::default()
10871            };
10872
10873            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10874        })
10875        .next()
10876        .await
10877        .unwrap();
10878
10879    cx.condition(&editor, |editor, _| editor.context_menu_visible())
10880        .await;
10881
10882    editor
10883        .update_in(cx, |editor, window, cx| {
10884            editor
10885                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10886                .unwrap()
10887        })
10888        .await
10889        .unwrap();
10890
10891    editor.update(cx, |editor, cx| {
10892        assert_text_with_selections(editor, expected_multibuffer, cx);
10893    })
10894}
10895
10896#[gpui::test]
10897async fn test_completion(cx: &mut TestAppContext) {
10898    init_test(cx, |_| {});
10899
10900    let mut cx = EditorLspTestContext::new_rust(
10901        lsp::ServerCapabilities {
10902            completion_provider: Some(lsp::CompletionOptions {
10903                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10904                resolve_provider: Some(true),
10905                ..Default::default()
10906            }),
10907            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10908            ..Default::default()
10909        },
10910        cx,
10911    )
10912    .await;
10913    let counter = Arc::new(AtomicUsize::new(0));
10914
10915    cx.set_state(indoc! {"
10916        oneˇ
10917        two
10918        three
10919    "});
10920    cx.simulate_keystroke(".");
10921    handle_completion_request(
10922        &mut cx,
10923        indoc! {"
10924            one.|<>
10925            two
10926            three
10927        "},
10928        vec!["first_completion", "second_completion"],
10929        counter.clone(),
10930    )
10931    .await;
10932    cx.condition(|editor, _| editor.context_menu_visible())
10933        .await;
10934    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10935
10936    let _handler = handle_signature_help_request(
10937        &mut cx,
10938        lsp::SignatureHelp {
10939            signatures: vec![lsp::SignatureInformation {
10940                label: "test signature".to_string(),
10941                documentation: None,
10942                parameters: Some(vec![lsp::ParameterInformation {
10943                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10944                    documentation: None,
10945                }]),
10946                active_parameter: None,
10947            }],
10948            active_signature: None,
10949            active_parameter: None,
10950        },
10951    );
10952    cx.update_editor(|editor, window, cx| {
10953        assert!(
10954            !editor.signature_help_state.is_shown(),
10955            "No signature help was called for"
10956        );
10957        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10958    });
10959    cx.run_until_parked();
10960    cx.update_editor(|editor, _, _| {
10961        assert!(
10962            !editor.signature_help_state.is_shown(),
10963            "No signature help should be shown when completions menu is open"
10964        );
10965    });
10966
10967    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10968        editor.context_menu_next(&Default::default(), window, cx);
10969        editor
10970            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10971            .unwrap()
10972    });
10973    cx.assert_editor_state(indoc! {"
10974        one.second_completionˇ
10975        two
10976        three
10977    "});
10978
10979    handle_resolve_completion_request(
10980        &mut cx,
10981        Some(vec![
10982            (
10983                //This overlaps with the primary completion edit which is
10984                //misbehavior from the LSP spec, test that we filter it out
10985                indoc! {"
10986                    one.second_ˇcompletion
10987                    two
10988                    threeˇ
10989                "},
10990                "overlapping additional edit",
10991            ),
10992            (
10993                indoc! {"
10994                    one.second_completion
10995                    two
10996                    threeˇ
10997                "},
10998                "\nadditional edit",
10999            ),
11000        ]),
11001    )
11002    .await;
11003    apply_additional_edits.await.unwrap();
11004    cx.assert_editor_state(indoc! {"
11005        one.second_completionˇ
11006        two
11007        three
11008        additional edit
11009    "});
11010
11011    cx.set_state(indoc! {"
11012        one.second_completion
11013        twoˇ
11014        threeˇ
11015        additional edit
11016    "});
11017    cx.simulate_keystroke(" ");
11018    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11019    cx.simulate_keystroke("s");
11020    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11021
11022    cx.assert_editor_state(indoc! {"
11023        one.second_completion
11024        two sˇ
11025        three sˇ
11026        additional edit
11027    "});
11028    handle_completion_request(
11029        &mut cx,
11030        indoc! {"
11031            one.second_completion
11032            two s
11033            three <s|>
11034            additional edit
11035        "},
11036        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11037        counter.clone(),
11038    )
11039    .await;
11040    cx.condition(|editor, _| editor.context_menu_visible())
11041        .await;
11042    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11043
11044    cx.simulate_keystroke("i");
11045
11046    handle_completion_request(
11047        &mut cx,
11048        indoc! {"
11049            one.second_completion
11050            two si
11051            three <si|>
11052            additional edit
11053        "},
11054        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11055        counter.clone(),
11056    )
11057    .await;
11058    cx.condition(|editor, _| editor.context_menu_visible())
11059        .await;
11060    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11061
11062    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11063        editor
11064            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11065            .unwrap()
11066    });
11067    cx.assert_editor_state(indoc! {"
11068        one.second_completion
11069        two sixth_completionˇ
11070        three sixth_completionˇ
11071        additional edit
11072    "});
11073
11074    apply_additional_edits.await.unwrap();
11075
11076    update_test_language_settings(&mut cx, |settings| {
11077        settings.defaults.show_completions_on_input = Some(false);
11078    });
11079    cx.set_state("editorˇ");
11080    cx.simulate_keystroke(".");
11081    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11082    cx.simulate_keystrokes("c l o");
11083    cx.assert_editor_state("editor.cloˇ");
11084    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11085    cx.update_editor(|editor, window, cx| {
11086        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11087    });
11088    handle_completion_request(
11089        &mut cx,
11090        "editor.<clo|>",
11091        vec!["close", "clobber"],
11092        counter.clone(),
11093    )
11094    .await;
11095    cx.condition(|editor, _| editor.context_menu_visible())
11096        .await;
11097    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11098
11099    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11100        editor
11101            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11102            .unwrap()
11103    });
11104    cx.assert_editor_state("editor.closeˇ");
11105    handle_resolve_completion_request(&mut cx, None).await;
11106    apply_additional_edits.await.unwrap();
11107}
11108
11109#[gpui::test]
11110async fn test_word_completion(cx: &mut TestAppContext) {
11111    let lsp_fetch_timeout_ms = 10;
11112    init_test(cx, |language_settings| {
11113        language_settings.defaults.completions = Some(CompletionSettings {
11114            words: WordsCompletionMode::Fallback,
11115            lsp: true,
11116            lsp_fetch_timeout_ms: 10,
11117            lsp_insert_mode: LspInsertMode::Insert,
11118        });
11119    });
11120
11121    let mut cx = EditorLspTestContext::new_rust(
11122        lsp::ServerCapabilities {
11123            completion_provider: Some(lsp::CompletionOptions {
11124                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11125                ..lsp::CompletionOptions::default()
11126            }),
11127            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11128            ..lsp::ServerCapabilities::default()
11129        },
11130        cx,
11131    )
11132    .await;
11133
11134    let throttle_completions = Arc::new(AtomicBool::new(false));
11135
11136    let lsp_throttle_completions = throttle_completions.clone();
11137    let _completion_requests_handler =
11138        cx.lsp
11139            .server
11140            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11141                let lsp_throttle_completions = lsp_throttle_completions.clone();
11142                let cx = cx.clone();
11143                async move {
11144                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11145                        cx.background_executor()
11146                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11147                            .await;
11148                    }
11149                    Ok(Some(lsp::CompletionResponse::Array(vec![
11150                        lsp::CompletionItem {
11151                            label: "first".into(),
11152                            ..lsp::CompletionItem::default()
11153                        },
11154                        lsp::CompletionItem {
11155                            label: "last".into(),
11156                            ..lsp::CompletionItem::default()
11157                        },
11158                    ])))
11159                }
11160            });
11161
11162    cx.set_state(indoc! {"
11163        oneˇ
11164        two
11165        three
11166    "});
11167    cx.simulate_keystroke(".");
11168    cx.executor().run_until_parked();
11169    cx.condition(|editor, _| editor.context_menu_visible())
11170        .await;
11171    cx.update_editor(|editor, window, cx| {
11172        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11173        {
11174            assert_eq!(
11175                completion_menu_entries(&menu),
11176                &["first", "last"],
11177                "When LSP server is fast to reply, no fallback word completions are used"
11178            );
11179        } else {
11180            panic!("expected completion menu to be open");
11181        }
11182        editor.cancel(&Cancel, window, cx);
11183    });
11184    cx.executor().run_until_parked();
11185    cx.condition(|editor, _| !editor.context_menu_visible())
11186        .await;
11187
11188    throttle_completions.store(true, atomic::Ordering::Release);
11189    cx.simulate_keystroke(".");
11190    cx.executor()
11191        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11192    cx.executor().run_until_parked();
11193    cx.condition(|editor, _| editor.context_menu_visible())
11194        .await;
11195    cx.update_editor(|editor, _, _| {
11196        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11197        {
11198            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11199                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11200        } else {
11201            panic!("expected completion menu to be open");
11202        }
11203    });
11204}
11205
11206#[gpui::test]
11207async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11208    init_test(cx, |language_settings| {
11209        language_settings.defaults.completions = Some(CompletionSettings {
11210            words: WordsCompletionMode::Enabled,
11211            lsp: true,
11212            lsp_fetch_timeout_ms: 0,
11213            lsp_insert_mode: LspInsertMode::Insert,
11214        });
11215    });
11216
11217    let mut cx = EditorLspTestContext::new_rust(
11218        lsp::ServerCapabilities {
11219            completion_provider: Some(lsp::CompletionOptions {
11220                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11221                ..lsp::CompletionOptions::default()
11222            }),
11223            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11224            ..lsp::ServerCapabilities::default()
11225        },
11226        cx,
11227    )
11228    .await;
11229
11230    let _completion_requests_handler =
11231        cx.lsp
11232            .server
11233            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11234                Ok(Some(lsp::CompletionResponse::Array(vec![
11235                    lsp::CompletionItem {
11236                        label: "first".into(),
11237                        ..lsp::CompletionItem::default()
11238                    },
11239                    lsp::CompletionItem {
11240                        label: "last".into(),
11241                        ..lsp::CompletionItem::default()
11242                    },
11243                ])))
11244            });
11245
11246    cx.set_state(indoc! {"ˇ
11247        first
11248        last
11249        second
11250    "});
11251    cx.simulate_keystroke(".");
11252    cx.executor().run_until_parked();
11253    cx.condition(|editor, _| editor.context_menu_visible())
11254        .await;
11255    cx.update_editor(|editor, _, _| {
11256        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11257        {
11258            assert_eq!(
11259                completion_menu_entries(&menu),
11260                &["first", "last", "second"],
11261                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11262            );
11263        } else {
11264            panic!("expected completion menu to be open");
11265        }
11266    });
11267}
11268
11269#[gpui::test]
11270async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11271    init_test(cx, |language_settings| {
11272        language_settings.defaults.completions = Some(CompletionSettings {
11273            words: WordsCompletionMode::Disabled,
11274            lsp: true,
11275            lsp_fetch_timeout_ms: 0,
11276            lsp_insert_mode: LspInsertMode::Insert,
11277        });
11278    });
11279
11280    let mut cx = EditorLspTestContext::new_rust(
11281        lsp::ServerCapabilities {
11282            completion_provider: Some(lsp::CompletionOptions {
11283                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11284                ..lsp::CompletionOptions::default()
11285            }),
11286            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11287            ..lsp::ServerCapabilities::default()
11288        },
11289        cx,
11290    )
11291    .await;
11292
11293    let _completion_requests_handler =
11294        cx.lsp
11295            .server
11296            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11297                panic!("LSP completions should not be queried when dealing with word completions")
11298            });
11299
11300    cx.set_state(indoc! {"ˇ
11301        first
11302        last
11303        second
11304    "});
11305    cx.update_editor(|editor, window, cx| {
11306        editor.show_word_completions(&ShowWordCompletions, window, cx);
11307    });
11308    cx.executor().run_until_parked();
11309    cx.condition(|editor, _| editor.context_menu_visible())
11310        .await;
11311    cx.update_editor(|editor, _, _| {
11312        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11313        {
11314            assert_eq!(
11315                completion_menu_entries(&menu),
11316                &["first", "last", "second"],
11317                "`ShowWordCompletions` action should show word completions"
11318            );
11319        } else {
11320            panic!("expected completion menu to be open");
11321        }
11322    });
11323
11324    cx.simulate_keystroke("l");
11325    cx.executor().run_until_parked();
11326    cx.condition(|editor, _| editor.context_menu_visible())
11327        .await;
11328    cx.update_editor(|editor, _, _| {
11329        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11330        {
11331            assert_eq!(
11332                completion_menu_entries(&menu),
11333                &["last"],
11334                "After showing word completions, further editing should filter them and not query the LSP"
11335            );
11336        } else {
11337            panic!("expected completion menu to be open");
11338        }
11339    });
11340}
11341
11342#[gpui::test]
11343async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11344    init_test(cx, |language_settings| {
11345        language_settings.defaults.completions = Some(CompletionSettings {
11346            words: WordsCompletionMode::Fallback,
11347            lsp: false,
11348            lsp_fetch_timeout_ms: 0,
11349            lsp_insert_mode: LspInsertMode::Insert,
11350        });
11351    });
11352
11353    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11354
11355    cx.set_state(indoc! {"ˇ
11356        0_usize
11357        let
11358        33
11359        4.5f32
11360    "});
11361    cx.update_editor(|editor, window, cx| {
11362        editor.show_completions(&ShowCompletions::default(), window, cx);
11363    });
11364    cx.executor().run_until_parked();
11365    cx.condition(|editor, _| editor.context_menu_visible())
11366        .await;
11367    cx.update_editor(|editor, window, cx| {
11368        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11369        {
11370            assert_eq!(
11371                completion_menu_entries(&menu),
11372                &["let"],
11373                "With no digits in the completion query, no digits should be in the word completions"
11374            );
11375        } else {
11376            panic!("expected completion menu to be open");
11377        }
11378        editor.cancel(&Cancel, window, cx);
11379    });
11380
11381    cx.set_state(indoc! {"11382        0_usize
11383        let
11384        3
11385        33.35f32
11386    "});
11387    cx.update_editor(|editor, window, cx| {
11388        editor.show_completions(&ShowCompletions::default(), window, cx);
11389    });
11390    cx.executor().run_until_parked();
11391    cx.condition(|editor, _| editor.context_menu_visible())
11392        .await;
11393    cx.update_editor(|editor, _, _| {
11394        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11395        {
11396            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11397                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11398        } else {
11399            panic!("expected completion menu to be open");
11400        }
11401    });
11402}
11403
11404fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11405    let position = || lsp::Position {
11406        line: params.text_document_position.position.line,
11407        character: params.text_document_position.position.character,
11408    };
11409    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11410        range: lsp::Range {
11411            start: position(),
11412            end: position(),
11413        },
11414        new_text: text.to_string(),
11415    }))
11416}
11417
11418#[gpui::test]
11419async fn test_multiline_completion(cx: &mut TestAppContext) {
11420    init_test(cx, |_| {});
11421
11422    let fs = FakeFs::new(cx.executor());
11423    fs.insert_tree(
11424        path!("/a"),
11425        json!({
11426            "main.ts": "a",
11427        }),
11428    )
11429    .await;
11430
11431    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11432    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11433    let typescript_language = Arc::new(Language::new(
11434        LanguageConfig {
11435            name: "TypeScript".into(),
11436            matcher: LanguageMatcher {
11437                path_suffixes: vec!["ts".to_string()],
11438                ..LanguageMatcher::default()
11439            },
11440            line_comments: vec!["// ".into()],
11441            ..LanguageConfig::default()
11442        },
11443        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11444    ));
11445    language_registry.add(typescript_language.clone());
11446    let mut fake_servers = language_registry.register_fake_lsp(
11447        "TypeScript",
11448        FakeLspAdapter {
11449            capabilities: lsp::ServerCapabilities {
11450                completion_provider: Some(lsp::CompletionOptions {
11451                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11452                    ..lsp::CompletionOptions::default()
11453                }),
11454                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11455                ..lsp::ServerCapabilities::default()
11456            },
11457            // Emulate vtsls label generation
11458            label_for_completion: Some(Box::new(|item, _| {
11459                let text = if let Some(description) = item
11460                    .label_details
11461                    .as_ref()
11462                    .and_then(|label_details| label_details.description.as_ref())
11463                {
11464                    format!("{} {}", item.label, description)
11465                } else if let Some(detail) = &item.detail {
11466                    format!("{} {}", item.label, detail)
11467                } else {
11468                    item.label.clone()
11469                };
11470                let len = text.len();
11471                Some(language::CodeLabel {
11472                    text,
11473                    runs: Vec::new(),
11474                    filter_range: 0..len,
11475                })
11476            })),
11477            ..FakeLspAdapter::default()
11478        },
11479    );
11480    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11481    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11482    let worktree_id = workspace
11483        .update(cx, |workspace, _window, cx| {
11484            workspace.project().update(cx, |project, cx| {
11485                project.worktrees(cx).next().unwrap().read(cx).id()
11486            })
11487        })
11488        .unwrap();
11489    let _buffer = project
11490        .update(cx, |project, cx| {
11491            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11492        })
11493        .await
11494        .unwrap();
11495    let editor = workspace
11496        .update(cx, |workspace, window, cx| {
11497            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11498        })
11499        .unwrap()
11500        .await
11501        .unwrap()
11502        .downcast::<Editor>()
11503        .unwrap();
11504    let fake_server = fake_servers.next().await.unwrap();
11505
11506    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11507    let multiline_label_2 = "a\nb\nc\n";
11508    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11509    let multiline_description = "d\ne\nf\n";
11510    let multiline_detail_2 = "g\nh\ni\n";
11511
11512    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11513        move |params, _| async move {
11514            Ok(Some(lsp::CompletionResponse::Array(vec![
11515                lsp::CompletionItem {
11516                    label: multiline_label.to_string(),
11517                    text_edit: gen_text_edit(&params, "new_text_1"),
11518                    ..lsp::CompletionItem::default()
11519                },
11520                lsp::CompletionItem {
11521                    label: "single line label 1".to_string(),
11522                    detail: Some(multiline_detail.to_string()),
11523                    text_edit: gen_text_edit(&params, "new_text_2"),
11524                    ..lsp::CompletionItem::default()
11525                },
11526                lsp::CompletionItem {
11527                    label: "single line label 2".to_string(),
11528                    label_details: Some(lsp::CompletionItemLabelDetails {
11529                        description: Some(multiline_description.to_string()),
11530                        detail: None,
11531                    }),
11532                    text_edit: gen_text_edit(&params, "new_text_2"),
11533                    ..lsp::CompletionItem::default()
11534                },
11535                lsp::CompletionItem {
11536                    label: multiline_label_2.to_string(),
11537                    detail: Some(multiline_detail_2.to_string()),
11538                    text_edit: gen_text_edit(&params, "new_text_3"),
11539                    ..lsp::CompletionItem::default()
11540                },
11541                lsp::CompletionItem {
11542                    label: "Label with many     spaces and \t but without newlines".to_string(),
11543                    detail: Some(
11544                        "Details with many     spaces and \t but without newlines".to_string(),
11545                    ),
11546                    text_edit: gen_text_edit(&params, "new_text_4"),
11547                    ..lsp::CompletionItem::default()
11548                },
11549            ])))
11550        },
11551    );
11552
11553    editor.update_in(cx, |editor, window, cx| {
11554        cx.focus_self(window);
11555        editor.move_to_end(&MoveToEnd, window, cx);
11556        editor.handle_input(".", window, cx);
11557    });
11558    cx.run_until_parked();
11559    completion_handle.next().await.unwrap();
11560
11561    editor.update(cx, |editor, _| {
11562        assert!(editor.context_menu_visible());
11563        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11564        {
11565            let completion_labels = menu
11566                .completions
11567                .borrow()
11568                .iter()
11569                .map(|c| c.label.text.clone())
11570                .collect::<Vec<_>>();
11571            assert_eq!(
11572                completion_labels,
11573                &[
11574                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11575                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11576                    "single line label 2 d e f ",
11577                    "a b c g h i ",
11578                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11579                ],
11580                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11581            );
11582
11583            for completion in menu
11584                .completions
11585                .borrow()
11586                .iter() {
11587                    assert_eq!(
11588                        completion.label.filter_range,
11589                        0..completion.label.text.len(),
11590                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11591                    );
11592                }
11593        } else {
11594            panic!("expected completion menu to be open");
11595        }
11596    });
11597}
11598
11599#[gpui::test]
11600async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11601    init_test(cx, |_| {});
11602    let mut cx = EditorLspTestContext::new_rust(
11603        lsp::ServerCapabilities {
11604            completion_provider: Some(lsp::CompletionOptions {
11605                trigger_characters: Some(vec![".".to_string()]),
11606                ..Default::default()
11607            }),
11608            ..Default::default()
11609        },
11610        cx,
11611    )
11612    .await;
11613    cx.lsp
11614        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11615            Ok(Some(lsp::CompletionResponse::Array(vec![
11616                lsp::CompletionItem {
11617                    label: "first".into(),
11618                    ..Default::default()
11619                },
11620                lsp::CompletionItem {
11621                    label: "last".into(),
11622                    ..Default::default()
11623                },
11624            ])))
11625        });
11626    cx.set_state("variableˇ");
11627    cx.simulate_keystroke(".");
11628    cx.executor().run_until_parked();
11629
11630    cx.update_editor(|editor, _, _| {
11631        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11632        {
11633            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11634        } else {
11635            panic!("expected completion menu to be open");
11636        }
11637    });
11638
11639    cx.update_editor(|editor, window, cx| {
11640        editor.move_page_down(&MovePageDown::default(), window, cx);
11641        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11642        {
11643            assert!(
11644                menu.selected_item == 1,
11645                "expected PageDown to select the last item from the context menu"
11646            );
11647        } else {
11648            panic!("expected completion menu to stay open after PageDown");
11649        }
11650    });
11651
11652    cx.update_editor(|editor, window, cx| {
11653        editor.move_page_up(&MovePageUp::default(), window, cx);
11654        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11655        {
11656            assert!(
11657                menu.selected_item == 0,
11658                "expected PageUp to select the first item from the context menu"
11659            );
11660        } else {
11661            panic!("expected completion menu to stay open after PageUp");
11662        }
11663    });
11664}
11665
11666#[gpui::test]
11667async fn test_as_is_completions(cx: &mut TestAppContext) {
11668    init_test(cx, |_| {});
11669    let mut cx = EditorLspTestContext::new_rust(
11670        lsp::ServerCapabilities {
11671            completion_provider: Some(lsp::CompletionOptions {
11672                ..Default::default()
11673            }),
11674            ..Default::default()
11675        },
11676        cx,
11677    )
11678    .await;
11679    cx.lsp
11680        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11681            Ok(Some(lsp::CompletionResponse::Array(vec![
11682                lsp::CompletionItem {
11683                    label: "unsafe".into(),
11684                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11685                        range: lsp::Range {
11686                            start: lsp::Position {
11687                                line: 1,
11688                                character: 2,
11689                            },
11690                            end: lsp::Position {
11691                                line: 1,
11692                                character: 3,
11693                            },
11694                        },
11695                        new_text: "unsafe".to_string(),
11696                    })),
11697                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11698                    ..Default::default()
11699                },
11700            ])))
11701        });
11702    cx.set_state("fn a() {}\n");
11703    cx.executor().run_until_parked();
11704    cx.update_editor(|editor, window, cx| {
11705        editor.show_completions(
11706            &ShowCompletions {
11707                trigger: Some("\n".into()),
11708            },
11709            window,
11710            cx,
11711        );
11712    });
11713    cx.executor().run_until_parked();
11714
11715    cx.update_editor(|editor, window, cx| {
11716        editor.confirm_completion(&Default::default(), window, cx)
11717    });
11718    cx.executor().run_until_parked();
11719    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11720}
11721
11722#[gpui::test]
11723async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11724    init_test(cx, |_| {});
11725
11726    let mut cx = EditorLspTestContext::new_rust(
11727        lsp::ServerCapabilities {
11728            completion_provider: Some(lsp::CompletionOptions {
11729                trigger_characters: Some(vec![".".to_string()]),
11730                resolve_provider: Some(true),
11731                ..Default::default()
11732            }),
11733            ..Default::default()
11734        },
11735        cx,
11736    )
11737    .await;
11738
11739    cx.set_state("fn main() { let a = 2ˇ; }");
11740    cx.simulate_keystroke(".");
11741    let completion_item = lsp::CompletionItem {
11742        label: "Some".into(),
11743        kind: Some(lsp::CompletionItemKind::SNIPPET),
11744        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11745        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11746            kind: lsp::MarkupKind::Markdown,
11747            value: "```rust\nSome(2)\n```".to_string(),
11748        })),
11749        deprecated: Some(false),
11750        sort_text: Some("Some".to_string()),
11751        filter_text: Some("Some".to_string()),
11752        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11753        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11754            range: lsp::Range {
11755                start: lsp::Position {
11756                    line: 0,
11757                    character: 22,
11758                },
11759                end: lsp::Position {
11760                    line: 0,
11761                    character: 22,
11762                },
11763            },
11764            new_text: "Some(2)".to_string(),
11765        })),
11766        additional_text_edits: Some(vec![lsp::TextEdit {
11767            range: lsp::Range {
11768                start: lsp::Position {
11769                    line: 0,
11770                    character: 20,
11771                },
11772                end: lsp::Position {
11773                    line: 0,
11774                    character: 22,
11775                },
11776            },
11777            new_text: "".to_string(),
11778        }]),
11779        ..Default::default()
11780    };
11781
11782    let closure_completion_item = completion_item.clone();
11783    let counter = Arc::new(AtomicUsize::new(0));
11784    let counter_clone = counter.clone();
11785    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11786        let task_completion_item = closure_completion_item.clone();
11787        counter_clone.fetch_add(1, atomic::Ordering::Release);
11788        async move {
11789            Ok(Some(lsp::CompletionResponse::Array(vec![
11790                task_completion_item,
11791            ])))
11792        }
11793    });
11794
11795    cx.condition(|editor, _| editor.context_menu_visible())
11796        .await;
11797    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11798    assert!(request.next().await.is_some());
11799    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11800
11801    cx.simulate_keystrokes("S o m");
11802    cx.condition(|editor, _| editor.context_menu_visible())
11803        .await;
11804    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11805    assert!(request.next().await.is_some());
11806    assert!(request.next().await.is_some());
11807    assert!(request.next().await.is_some());
11808    request.close();
11809    assert!(request.next().await.is_none());
11810    assert_eq!(
11811        counter.load(atomic::Ordering::Acquire),
11812        4,
11813        "With the completions menu open, only one LSP request should happen per input"
11814    );
11815}
11816
11817#[gpui::test]
11818async fn test_toggle_comment(cx: &mut TestAppContext) {
11819    init_test(cx, |_| {});
11820    let mut cx = EditorTestContext::new(cx).await;
11821    let language = Arc::new(Language::new(
11822        LanguageConfig {
11823            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11824            ..Default::default()
11825        },
11826        Some(tree_sitter_rust::LANGUAGE.into()),
11827    ));
11828    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11829
11830    // If multiple selections intersect a line, the line is only toggled once.
11831    cx.set_state(indoc! {"
11832        fn a() {
11833            «//b();
11834            ˇ»// «c();
11835            //ˇ»  d();
11836        }
11837    "});
11838
11839    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11840
11841    cx.assert_editor_state(indoc! {"
11842        fn a() {
11843            «b();
11844            c();
11845            ˇ» d();
11846        }
11847    "});
11848
11849    // The comment prefix is inserted at the same column for every line in a
11850    // selection.
11851    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11852
11853    cx.assert_editor_state(indoc! {"
11854        fn a() {
11855            // «b();
11856            // c();
11857            ˇ»//  d();
11858        }
11859    "});
11860
11861    // If a selection ends at the beginning of a line, that line is not toggled.
11862    cx.set_selections_state(indoc! {"
11863        fn a() {
11864            // b();
11865            «// c();
11866        ˇ»    //  d();
11867        }
11868    "});
11869
11870    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11871
11872    cx.assert_editor_state(indoc! {"
11873        fn a() {
11874            // b();
11875            «c();
11876        ˇ»    //  d();
11877        }
11878    "});
11879
11880    // If a selection span a single line and is empty, the line is toggled.
11881    cx.set_state(indoc! {"
11882        fn a() {
11883            a();
11884            b();
11885        ˇ
11886        }
11887    "});
11888
11889    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11890
11891    cx.assert_editor_state(indoc! {"
11892        fn a() {
11893            a();
11894            b();
11895        //•ˇ
11896        }
11897    "});
11898
11899    // If a selection span multiple lines, empty lines are not toggled.
11900    cx.set_state(indoc! {"
11901        fn a() {
11902            «a();
11903
11904            c();ˇ»
11905        }
11906    "});
11907
11908    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11909
11910    cx.assert_editor_state(indoc! {"
11911        fn a() {
11912            // «a();
11913
11914            // c();ˇ»
11915        }
11916    "});
11917
11918    // If a selection includes multiple comment prefixes, all lines are uncommented.
11919    cx.set_state(indoc! {"
11920        fn a() {
11921            «// a();
11922            /// b();
11923            //! c();ˇ»
11924        }
11925    "});
11926
11927    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11928
11929    cx.assert_editor_state(indoc! {"
11930        fn a() {
11931            «a();
11932            b();
11933            c();ˇ»
11934        }
11935    "});
11936}
11937
11938#[gpui::test]
11939async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11940    init_test(cx, |_| {});
11941    let mut cx = EditorTestContext::new(cx).await;
11942    let language = Arc::new(Language::new(
11943        LanguageConfig {
11944            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11945            ..Default::default()
11946        },
11947        Some(tree_sitter_rust::LANGUAGE.into()),
11948    ));
11949    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11950
11951    let toggle_comments = &ToggleComments {
11952        advance_downwards: false,
11953        ignore_indent: true,
11954    };
11955
11956    // If multiple selections intersect a line, the line is only toggled once.
11957    cx.set_state(indoc! {"
11958        fn a() {
11959        //    «b();
11960        //    c();
11961        //    ˇ» d();
11962        }
11963    "});
11964
11965    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11966
11967    cx.assert_editor_state(indoc! {"
11968        fn a() {
11969            «b();
11970            c();
11971            ˇ» d();
11972        }
11973    "});
11974
11975    // The comment prefix is inserted at the beginning of each line
11976    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11977
11978    cx.assert_editor_state(indoc! {"
11979        fn a() {
11980        //    «b();
11981        //    c();
11982        //    ˇ» d();
11983        }
11984    "});
11985
11986    // If a selection ends at the beginning of a line, that line is not toggled.
11987    cx.set_selections_state(indoc! {"
11988        fn a() {
11989        //    b();
11990        //    «c();
11991        ˇ»//     d();
11992        }
11993    "});
11994
11995    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11996
11997    cx.assert_editor_state(indoc! {"
11998        fn a() {
11999        //    b();
12000            «c();
12001        ˇ»//     d();
12002        }
12003    "});
12004
12005    // If a selection span a single line and is empty, the line is toggled.
12006    cx.set_state(indoc! {"
12007        fn a() {
12008            a();
12009            b();
12010        ˇ
12011        }
12012    "});
12013
12014    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12015
12016    cx.assert_editor_state(indoc! {"
12017        fn a() {
12018            a();
12019            b();
12020        //ˇ
12021        }
12022    "});
12023
12024    // If a selection span multiple lines, empty lines are not toggled.
12025    cx.set_state(indoc! {"
12026        fn a() {
12027            «a();
12028
12029            c();ˇ»
12030        }
12031    "});
12032
12033    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12034
12035    cx.assert_editor_state(indoc! {"
12036        fn a() {
12037        //    «a();
12038
12039        //    c();ˇ»
12040        }
12041    "});
12042
12043    // If a selection includes multiple comment prefixes, all lines are uncommented.
12044    cx.set_state(indoc! {"
12045        fn a() {
12046        //    «a();
12047        ///    b();
12048        //!    c();ˇ»
12049        }
12050    "});
12051
12052    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12053
12054    cx.assert_editor_state(indoc! {"
12055        fn a() {
12056            «a();
12057            b();
12058            c();ˇ»
12059        }
12060    "});
12061}
12062
12063#[gpui::test]
12064async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12065    init_test(cx, |_| {});
12066
12067    let language = Arc::new(Language::new(
12068        LanguageConfig {
12069            line_comments: vec!["// ".into()],
12070            ..Default::default()
12071        },
12072        Some(tree_sitter_rust::LANGUAGE.into()),
12073    ));
12074
12075    let mut cx = EditorTestContext::new(cx).await;
12076
12077    cx.language_registry().add(language.clone());
12078    cx.update_buffer(|buffer, cx| {
12079        buffer.set_language(Some(language), cx);
12080    });
12081
12082    let toggle_comments = &ToggleComments {
12083        advance_downwards: true,
12084        ignore_indent: false,
12085    };
12086
12087    // Single cursor on one line -> advance
12088    // Cursor moves horizontally 3 characters as well on non-blank line
12089    cx.set_state(indoc!(
12090        "fn a() {
12091             ˇdog();
12092             cat();
12093        }"
12094    ));
12095    cx.update_editor(|editor, window, cx| {
12096        editor.toggle_comments(toggle_comments, window, cx);
12097    });
12098    cx.assert_editor_state(indoc!(
12099        "fn a() {
12100             // dog();
12101             catˇ();
12102        }"
12103    ));
12104
12105    // Single selection on one line -> don't advance
12106    cx.set_state(indoc!(
12107        "fn a() {
12108             «dog()ˇ»;
12109             cat();
12110        }"
12111    ));
12112    cx.update_editor(|editor, window, cx| {
12113        editor.toggle_comments(toggle_comments, window, cx);
12114    });
12115    cx.assert_editor_state(indoc!(
12116        "fn a() {
12117             // «dog()ˇ»;
12118             cat();
12119        }"
12120    ));
12121
12122    // Multiple cursors on one line -> advance
12123    cx.set_state(indoc!(
12124        "fn a() {
12125             ˇdˇog();
12126             cat();
12127        }"
12128    ));
12129    cx.update_editor(|editor, window, cx| {
12130        editor.toggle_comments(toggle_comments, window, cx);
12131    });
12132    cx.assert_editor_state(indoc!(
12133        "fn a() {
12134             // dog();
12135             catˇ(ˇ);
12136        }"
12137    ));
12138
12139    // Multiple cursors on one line, with selection -> don't advance
12140    cx.set_state(indoc!(
12141        "fn a() {
12142             ˇdˇog«()ˇ»;
12143             cat();
12144        }"
12145    ));
12146    cx.update_editor(|editor, window, cx| {
12147        editor.toggle_comments(toggle_comments, window, cx);
12148    });
12149    cx.assert_editor_state(indoc!(
12150        "fn a() {
12151             // ˇdˇog«()ˇ»;
12152             cat();
12153        }"
12154    ));
12155
12156    // Single cursor on one line -> advance
12157    // Cursor moves to column 0 on blank line
12158    cx.set_state(indoc!(
12159        "fn a() {
12160             ˇdog();
12161
12162             cat();
12163        }"
12164    ));
12165    cx.update_editor(|editor, window, cx| {
12166        editor.toggle_comments(toggle_comments, window, cx);
12167    });
12168    cx.assert_editor_state(indoc!(
12169        "fn a() {
12170             // dog();
12171        ˇ
12172             cat();
12173        }"
12174    ));
12175
12176    // Single cursor on one line -> advance
12177    // Cursor starts and ends at column 0
12178    cx.set_state(indoc!(
12179        "fn a() {
12180         ˇ    dog();
12181             cat();
12182        }"
12183    ));
12184    cx.update_editor(|editor, window, cx| {
12185        editor.toggle_comments(toggle_comments, window, cx);
12186    });
12187    cx.assert_editor_state(indoc!(
12188        "fn a() {
12189             // dog();
12190         ˇ    cat();
12191        }"
12192    ));
12193}
12194
12195#[gpui::test]
12196async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12197    init_test(cx, |_| {});
12198
12199    let mut cx = EditorTestContext::new(cx).await;
12200
12201    let html_language = Arc::new(
12202        Language::new(
12203            LanguageConfig {
12204                name: "HTML".into(),
12205                block_comment: Some(("<!-- ".into(), " -->".into())),
12206                ..Default::default()
12207            },
12208            Some(tree_sitter_html::LANGUAGE.into()),
12209        )
12210        .with_injection_query(
12211            r#"
12212            (script_element
12213                (raw_text) @injection.content
12214                (#set! injection.language "javascript"))
12215            "#,
12216        )
12217        .unwrap(),
12218    );
12219
12220    let javascript_language = Arc::new(Language::new(
12221        LanguageConfig {
12222            name: "JavaScript".into(),
12223            line_comments: vec!["// ".into()],
12224            ..Default::default()
12225        },
12226        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12227    ));
12228
12229    cx.language_registry().add(html_language.clone());
12230    cx.language_registry().add(javascript_language.clone());
12231    cx.update_buffer(|buffer, cx| {
12232        buffer.set_language(Some(html_language), cx);
12233    });
12234
12235    // Toggle comments for empty selections
12236    cx.set_state(
12237        &r#"
12238            <p>A</p>ˇ
12239            <p>B</p>ˇ
12240            <p>C</p>ˇ
12241        "#
12242        .unindent(),
12243    );
12244    cx.update_editor(|editor, window, cx| {
12245        editor.toggle_comments(&ToggleComments::default(), window, cx)
12246    });
12247    cx.assert_editor_state(
12248        &r#"
12249            <!-- <p>A</p>ˇ -->
12250            <!-- <p>B</p>ˇ -->
12251            <!-- <p>C</p>ˇ -->
12252        "#
12253        .unindent(),
12254    );
12255    cx.update_editor(|editor, window, cx| {
12256        editor.toggle_comments(&ToggleComments::default(), window, cx)
12257    });
12258    cx.assert_editor_state(
12259        &r#"
12260            <p>A</p>ˇ
12261            <p>B</p>ˇ
12262            <p>C</p>ˇ
12263        "#
12264        .unindent(),
12265    );
12266
12267    // Toggle comments for mixture of empty and non-empty selections, where
12268    // multiple selections occupy a given line.
12269    cx.set_state(
12270        &r#"
12271            <p>A«</p>
12272            <p>ˇ»B</p>ˇ
12273            <p>C«</p>
12274            <p>ˇ»D</p>ˇ
12275        "#
12276        .unindent(),
12277    );
12278
12279    cx.update_editor(|editor, window, cx| {
12280        editor.toggle_comments(&ToggleComments::default(), window, cx)
12281    });
12282    cx.assert_editor_state(
12283        &r#"
12284            <!-- <p>A«</p>
12285            <p>ˇ»B</p>ˇ -->
12286            <!-- <p>C«</p>
12287            <p>ˇ»D</p>ˇ -->
12288        "#
12289        .unindent(),
12290    );
12291    cx.update_editor(|editor, window, cx| {
12292        editor.toggle_comments(&ToggleComments::default(), window, cx)
12293    });
12294    cx.assert_editor_state(
12295        &r#"
12296            <p>A«</p>
12297            <p>ˇ»B</p>ˇ
12298            <p>C«</p>
12299            <p>ˇ»D</p>ˇ
12300        "#
12301        .unindent(),
12302    );
12303
12304    // Toggle comments when different languages are active for different
12305    // selections.
12306    cx.set_state(
12307        &r#"
12308            ˇ<script>
12309                ˇvar x = new Y();
12310            ˇ</script>
12311        "#
12312        .unindent(),
12313    );
12314    cx.executor().run_until_parked();
12315    cx.update_editor(|editor, window, cx| {
12316        editor.toggle_comments(&ToggleComments::default(), window, cx)
12317    });
12318    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12319    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12320    cx.assert_editor_state(
12321        &r#"
12322            <!-- ˇ<script> -->
12323                // ˇvar x = new Y();
12324            <!-- ˇ</script> -->
12325        "#
12326        .unindent(),
12327    );
12328}
12329
12330#[gpui::test]
12331fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12332    init_test(cx, |_| {});
12333
12334    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12335    let multibuffer = cx.new(|cx| {
12336        let mut multibuffer = MultiBuffer::new(ReadWrite);
12337        multibuffer.push_excerpts(
12338            buffer.clone(),
12339            [
12340                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12341                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12342            ],
12343            cx,
12344        );
12345        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12346        multibuffer
12347    });
12348
12349    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12350    editor.update_in(cx, |editor, window, cx| {
12351        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12352        editor.change_selections(None, window, cx, |s| {
12353            s.select_ranges([
12354                Point::new(0, 0)..Point::new(0, 0),
12355                Point::new(1, 0)..Point::new(1, 0),
12356            ])
12357        });
12358
12359        editor.handle_input("X", window, cx);
12360        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12361        assert_eq!(
12362            editor.selections.ranges(cx),
12363            [
12364                Point::new(0, 1)..Point::new(0, 1),
12365                Point::new(1, 1)..Point::new(1, 1),
12366            ]
12367        );
12368
12369        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12370        editor.change_selections(None, window, cx, |s| {
12371            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12372        });
12373        editor.backspace(&Default::default(), window, cx);
12374        assert_eq!(editor.text(cx), "Xa\nbbb");
12375        assert_eq!(
12376            editor.selections.ranges(cx),
12377            [Point::new(1, 0)..Point::new(1, 0)]
12378        );
12379
12380        editor.change_selections(None, window, cx, |s| {
12381            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12382        });
12383        editor.backspace(&Default::default(), window, cx);
12384        assert_eq!(editor.text(cx), "X\nbb");
12385        assert_eq!(
12386            editor.selections.ranges(cx),
12387            [Point::new(0, 1)..Point::new(0, 1)]
12388        );
12389    });
12390}
12391
12392#[gpui::test]
12393fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12394    init_test(cx, |_| {});
12395
12396    let markers = vec![('[', ']').into(), ('(', ')').into()];
12397    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12398        indoc! {"
12399            [aaaa
12400            (bbbb]
12401            cccc)",
12402        },
12403        markers.clone(),
12404    );
12405    let excerpt_ranges = markers.into_iter().map(|marker| {
12406        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12407        ExcerptRange::new(context.clone())
12408    });
12409    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12410    let multibuffer = cx.new(|cx| {
12411        let mut multibuffer = MultiBuffer::new(ReadWrite);
12412        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12413        multibuffer
12414    });
12415
12416    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12417    editor.update_in(cx, |editor, window, cx| {
12418        let (expected_text, selection_ranges) = marked_text_ranges(
12419            indoc! {"
12420                aaaa
12421                bˇbbb
12422                bˇbbˇb
12423                cccc"
12424            },
12425            true,
12426        );
12427        assert_eq!(editor.text(cx), expected_text);
12428        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12429
12430        editor.handle_input("X", window, cx);
12431
12432        let (expected_text, expected_selections) = marked_text_ranges(
12433            indoc! {"
12434                aaaa
12435                bXˇbbXb
12436                bXˇbbXˇb
12437                cccc"
12438            },
12439            false,
12440        );
12441        assert_eq!(editor.text(cx), expected_text);
12442        assert_eq!(editor.selections.ranges(cx), expected_selections);
12443
12444        editor.newline(&Newline, window, cx);
12445        let (expected_text, expected_selections) = marked_text_ranges(
12446            indoc! {"
12447                aaaa
12448                bX
12449                ˇbbX
12450                b
12451                bX
12452                ˇbbX
12453                ˇb
12454                cccc"
12455            },
12456            false,
12457        );
12458        assert_eq!(editor.text(cx), expected_text);
12459        assert_eq!(editor.selections.ranges(cx), expected_selections);
12460    });
12461}
12462
12463#[gpui::test]
12464fn test_refresh_selections(cx: &mut TestAppContext) {
12465    init_test(cx, |_| {});
12466
12467    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12468    let mut excerpt1_id = None;
12469    let multibuffer = cx.new(|cx| {
12470        let mut multibuffer = MultiBuffer::new(ReadWrite);
12471        excerpt1_id = multibuffer
12472            .push_excerpts(
12473                buffer.clone(),
12474                [
12475                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12476                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12477                ],
12478                cx,
12479            )
12480            .into_iter()
12481            .next();
12482        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12483        multibuffer
12484    });
12485
12486    let editor = cx.add_window(|window, cx| {
12487        let mut editor = build_editor(multibuffer.clone(), window, cx);
12488        let snapshot = editor.snapshot(window, cx);
12489        editor.change_selections(None, window, cx, |s| {
12490            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12491        });
12492        editor.begin_selection(
12493            Point::new(2, 1).to_display_point(&snapshot),
12494            true,
12495            1,
12496            window,
12497            cx,
12498        );
12499        assert_eq!(
12500            editor.selections.ranges(cx),
12501            [
12502                Point::new(1, 3)..Point::new(1, 3),
12503                Point::new(2, 1)..Point::new(2, 1),
12504            ]
12505        );
12506        editor
12507    });
12508
12509    // Refreshing selections is a no-op when excerpts haven't changed.
12510    _ = editor.update(cx, |editor, window, cx| {
12511        editor.change_selections(None, window, cx, |s| s.refresh());
12512        assert_eq!(
12513            editor.selections.ranges(cx),
12514            [
12515                Point::new(1, 3)..Point::new(1, 3),
12516                Point::new(2, 1)..Point::new(2, 1),
12517            ]
12518        );
12519    });
12520
12521    multibuffer.update(cx, |multibuffer, cx| {
12522        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12523    });
12524    _ = editor.update(cx, |editor, window, cx| {
12525        // Removing an excerpt causes the first selection to become degenerate.
12526        assert_eq!(
12527            editor.selections.ranges(cx),
12528            [
12529                Point::new(0, 0)..Point::new(0, 0),
12530                Point::new(0, 1)..Point::new(0, 1)
12531            ]
12532        );
12533
12534        // Refreshing selections will relocate the first selection to the original buffer
12535        // location.
12536        editor.change_selections(None, window, cx, |s| s.refresh());
12537        assert_eq!(
12538            editor.selections.ranges(cx),
12539            [
12540                Point::new(0, 1)..Point::new(0, 1),
12541                Point::new(0, 3)..Point::new(0, 3)
12542            ]
12543        );
12544        assert!(editor.selections.pending_anchor().is_some());
12545    });
12546}
12547
12548#[gpui::test]
12549fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12550    init_test(cx, |_| {});
12551
12552    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12553    let mut excerpt1_id = None;
12554    let multibuffer = cx.new(|cx| {
12555        let mut multibuffer = MultiBuffer::new(ReadWrite);
12556        excerpt1_id = multibuffer
12557            .push_excerpts(
12558                buffer.clone(),
12559                [
12560                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12561                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12562                ],
12563                cx,
12564            )
12565            .into_iter()
12566            .next();
12567        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12568        multibuffer
12569    });
12570
12571    let editor = cx.add_window(|window, cx| {
12572        let mut editor = build_editor(multibuffer.clone(), window, cx);
12573        let snapshot = editor.snapshot(window, cx);
12574        editor.begin_selection(
12575            Point::new(1, 3).to_display_point(&snapshot),
12576            false,
12577            1,
12578            window,
12579            cx,
12580        );
12581        assert_eq!(
12582            editor.selections.ranges(cx),
12583            [Point::new(1, 3)..Point::new(1, 3)]
12584        );
12585        editor
12586    });
12587
12588    multibuffer.update(cx, |multibuffer, cx| {
12589        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12590    });
12591    _ = editor.update(cx, |editor, window, cx| {
12592        assert_eq!(
12593            editor.selections.ranges(cx),
12594            [Point::new(0, 0)..Point::new(0, 0)]
12595        );
12596
12597        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12598        editor.change_selections(None, window, cx, |s| s.refresh());
12599        assert_eq!(
12600            editor.selections.ranges(cx),
12601            [Point::new(0, 3)..Point::new(0, 3)]
12602        );
12603        assert!(editor.selections.pending_anchor().is_some());
12604    });
12605}
12606
12607#[gpui::test]
12608async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12609    init_test(cx, |_| {});
12610
12611    let language = Arc::new(
12612        Language::new(
12613            LanguageConfig {
12614                brackets: BracketPairConfig {
12615                    pairs: vec![
12616                        BracketPair {
12617                            start: "{".to_string(),
12618                            end: "}".to_string(),
12619                            close: true,
12620                            surround: true,
12621                            newline: true,
12622                        },
12623                        BracketPair {
12624                            start: "/* ".to_string(),
12625                            end: " */".to_string(),
12626                            close: true,
12627                            surround: true,
12628                            newline: true,
12629                        },
12630                    ],
12631                    ..Default::default()
12632                },
12633                ..Default::default()
12634            },
12635            Some(tree_sitter_rust::LANGUAGE.into()),
12636        )
12637        .with_indents_query("")
12638        .unwrap(),
12639    );
12640
12641    let text = concat!(
12642        "{   }\n",     //
12643        "  x\n",       //
12644        "  /*   */\n", //
12645        "x\n",         //
12646        "{{} }\n",     //
12647    );
12648
12649    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12650    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12651    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12652    editor
12653        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12654        .await;
12655
12656    editor.update_in(cx, |editor, window, cx| {
12657        editor.change_selections(None, window, cx, |s| {
12658            s.select_display_ranges([
12659                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12660                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12661                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12662            ])
12663        });
12664        editor.newline(&Newline, window, cx);
12665
12666        assert_eq!(
12667            editor.buffer().read(cx).read(cx).text(),
12668            concat!(
12669                "{ \n",    // Suppress rustfmt
12670                "\n",      //
12671                "}\n",     //
12672                "  x\n",   //
12673                "  /* \n", //
12674                "  \n",    //
12675                "  */\n",  //
12676                "x\n",     //
12677                "{{} \n",  //
12678                "}\n",     //
12679            )
12680        );
12681    });
12682}
12683
12684#[gpui::test]
12685fn test_highlighted_ranges(cx: &mut TestAppContext) {
12686    init_test(cx, |_| {});
12687
12688    let editor = cx.add_window(|window, cx| {
12689        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12690        build_editor(buffer.clone(), window, cx)
12691    });
12692
12693    _ = editor.update(cx, |editor, window, cx| {
12694        struct Type1;
12695        struct Type2;
12696
12697        let buffer = editor.buffer.read(cx).snapshot(cx);
12698
12699        let anchor_range =
12700            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12701
12702        editor.highlight_background::<Type1>(
12703            &[
12704                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12705                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12706                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12707                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12708            ],
12709            |_| Hsla::red(),
12710            cx,
12711        );
12712        editor.highlight_background::<Type2>(
12713            &[
12714                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12715                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12716                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12717                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12718            ],
12719            |_| Hsla::green(),
12720            cx,
12721        );
12722
12723        let snapshot = editor.snapshot(window, cx);
12724        let mut highlighted_ranges = editor.background_highlights_in_range(
12725            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12726            &snapshot,
12727            cx.theme().colors(),
12728        );
12729        // Enforce a consistent ordering based on color without relying on the ordering of the
12730        // highlight's `TypeId` which is non-executor.
12731        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12732        assert_eq!(
12733            highlighted_ranges,
12734            &[
12735                (
12736                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12737                    Hsla::red(),
12738                ),
12739                (
12740                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12741                    Hsla::red(),
12742                ),
12743                (
12744                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12745                    Hsla::green(),
12746                ),
12747                (
12748                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12749                    Hsla::green(),
12750                ),
12751            ]
12752        );
12753        assert_eq!(
12754            editor.background_highlights_in_range(
12755                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12756                &snapshot,
12757                cx.theme().colors(),
12758            ),
12759            &[(
12760                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12761                Hsla::red(),
12762            )]
12763        );
12764    });
12765}
12766
12767#[gpui::test]
12768async fn test_following(cx: &mut TestAppContext) {
12769    init_test(cx, |_| {});
12770
12771    let fs = FakeFs::new(cx.executor());
12772    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12773
12774    let buffer = project.update(cx, |project, cx| {
12775        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12776        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12777    });
12778    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12779    let follower = cx.update(|cx| {
12780        cx.open_window(
12781            WindowOptions {
12782                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12783                    gpui::Point::new(px(0.), px(0.)),
12784                    gpui::Point::new(px(10.), px(80.)),
12785                ))),
12786                ..Default::default()
12787            },
12788            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12789        )
12790        .unwrap()
12791    });
12792
12793    let is_still_following = Rc::new(RefCell::new(true));
12794    let follower_edit_event_count = Rc::new(RefCell::new(0));
12795    let pending_update = Rc::new(RefCell::new(None));
12796    let leader_entity = leader.root(cx).unwrap();
12797    let follower_entity = follower.root(cx).unwrap();
12798    _ = follower.update(cx, {
12799        let update = pending_update.clone();
12800        let is_still_following = is_still_following.clone();
12801        let follower_edit_event_count = follower_edit_event_count.clone();
12802        |_, window, cx| {
12803            cx.subscribe_in(
12804                &leader_entity,
12805                window,
12806                move |_, leader, event, window, cx| {
12807                    leader.read(cx).add_event_to_update_proto(
12808                        event,
12809                        &mut update.borrow_mut(),
12810                        window,
12811                        cx,
12812                    );
12813                },
12814            )
12815            .detach();
12816
12817            cx.subscribe_in(
12818                &follower_entity,
12819                window,
12820                move |_, _, event: &EditorEvent, _window, _cx| {
12821                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12822                        *is_still_following.borrow_mut() = false;
12823                    }
12824
12825                    if let EditorEvent::BufferEdited = event {
12826                        *follower_edit_event_count.borrow_mut() += 1;
12827                    }
12828                },
12829            )
12830            .detach();
12831        }
12832    });
12833
12834    // Update the selections only
12835    _ = leader.update(cx, |leader, window, cx| {
12836        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12837    });
12838    follower
12839        .update(cx, |follower, window, cx| {
12840            follower.apply_update_proto(
12841                &project,
12842                pending_update.borrow_mut().take().unwrap(),
12843                window,
12844                cx,
12845            )
12846        })
12847        .unwrap()
12848        .await
12849        .unwrap();
12850    _ = follower.update(cx, |follower, _, cx| {
12851        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12852    });
12853    assert!(*is_still_following.borrow());
12854    assert_eq!(*follower_edit_event_count.borrow(), 0);
12855
12856    // Update the scroll position only
12857    _ = leader.update(cx, |leader, window, cx| {
12858        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12859    });
12860    follower
12861        .update(cx, |follower, window, cx| {
12862            follower.apply_update_proto(
12863                &project,
12864                pending_update.borrow_mut().take().unwrap(),
12865                window,
12866                cx,
12867            )
12868        })
12869        .unwrap()
12870        .await
12871        .unwrap();
12872    assert_eq!(
12873        follower
12874            .update(cx, |follower, _, cx| follower.scroll_position(cx))
12875            .unwrap(),
12876        gpui::Point::new(1.5, 3.5)
12877    );
12878    assert!(*is_still_following.borrow());
12879    assert_eq!(*follower_edit_event_count.borrow(), 0);
12880
12881    // Update the selections and scroll position. The follower's scroll position is updated
12882    // via autoscroll, not via the leader's exact scroll position.
12883    _ = leader.update(cx, |leader, window, cx| {
12884        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12885        leader.request_autoscroll(Autoscroll::newest(), cx);
12886        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12887    });
12888    follower
12889        .update(cx, |follower, window, cx| {
12890            follower.apply_update_proto(
12891                &project,
12892                pending_update.borrow_mut().take().unwrap(),
12893                window,
12894                cx,
12895            )
12896        })
12897        .unwrap()
12898        .await
12899        .unwrap();
12900    _ = follower.update(cx, |follower, _, cx| {
12901        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12902        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12903    });
12904    assert!(*is_still_following.borrow());
12905
12906    // Creating a pending selection that precedes another selection
12907    _ = leader.update(cx, |leader, window, cx| {
12908        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12909        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12910    });
12911    follower
12912        .update(cx, |follower, window, cx| {
12913            follower.apply_update_proto(
12914                &project,
12915                pending_update.borrow_mut().take().unwrap(),
12916                window,
12917                cx,
12918            )
12919        })
12920        .unwrap()
12921        .await
12922        .unwrap();
12923    _ = follower.update(cx, |follower, _, cx| {
12924        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12925    });
12926    assert!(*is_still_following.borrow());
12927
12928    // Extend the pending selection so that it surrounds another selection
12929    _ = leader.update(cx, |leader, window, cx| {
12930        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12931    });
12932    follower
12933        .update(cx, |follower, window, cx| {
12934            follower.apply_update_proto(
12935                &project,
12936                pending_update.borrow_mut().take().unwrap(),
12937                window,
12938                cx,
12939            )
12940        })
12941        .unwrap()
12942        .await
12943        .unwrap();
12944    _ = follower.update(cx, |follower, _, cx| {
12945        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12946    });
12947
12948    // Scrolling locally breaks the follow
12949    _ = follower.update(cx, |follower, window, cx| {
12950        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12951        follower.set_scroll_anchor(
12952            ScrollAnchor {
12953                anchor: top_anchor,
12954                offset: gpui::Point::new(0.0, 0.5),
12955            },
12956            window,
12957            cx,
12958        );
12959    });
12960    assert!(!(*is_still_following.borrow()));
12961}
12962
12963#[gpui::test]
12964async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12965    init_test(cx, |_| {});
12966
12967    let fs = FakeFs::new(cx.executor());
12968    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12969    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12970    let pane = workspace
12971        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12972        .unwrap();
12973
12974    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12975
12976    let leader = pane.update_in(cx, |_, window, cx| {
12977        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
12978        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
12979    });
12980
12981    // Start following the editor when it has no excerpts.
12982    let mut state_message =
12983        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
12984    let workspace_entity = workspace.root(cx).unwrap();
12985    let follower_1 = cx
12986        .update_window(*workspace.deref(), |_, window, cx| {
12987            Editor::from_state_proto(
12988                workspace_entity,
12989                ViewId {
12990                    creator: CollaboratorId::PeerId(PeerId::default()),
12991                    id: 0,
12992                },
12993                &mut state_message,
12994                window,
12995                cx,
12996            )
12997        })
12998        .unwrap()
12999        .unwrap()
13000        .await
13001        .unwrap();
13002
13003    let update_message = Rc::new(RefCell::new(None));
13004    follower_1.update_in(cx, {
13005        let update = update_message.clone();
13006        |_, window, cx| {
13007            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13008                leader.read(cx).add_event_to_update_proto(
13009                    event,
13010                    &mut update.borrow_mut(),
13011                    window,
13012                    cx,
13013                );
13014            })
13015            .detach();
13016        }
13017    });
13018
13019    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13020        (
13021            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13022            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13023        )
13024    });
13025
13026    // Insert some excerpts.
13027    leader.update(cx, |leader, cx| {
13028        leader.buffer.update(cx, |multibuffer, cx| {
13029            multibuffer.set_excerpts_for_path(
13030                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13031                buffer_1.clone(),
13032                vec![
13033                    Point::row_range(0..3),
13034                    Point::row_range(1..6),
13035                    Point::row_range(12..15),
13036                ],
13037                0,
13038                cx,
13039            );
13040            multibuffer.set_excerpts_for_path(
13041                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13042                buffer_2.clone(),
13043                vec![Point::row_range(0..6), Point::row_range(8..12)],
13044                0,
13045                cx,
13046            );
13047        });
13048    });
13049
13050    // Apply the update of adding the excerpts.
13051    follower_1
13052        .update_in(cx, |follower, window, cx| {
13053            follower.apply_update_proto(
13054                &project,
13055                update_message.borrow().clone().unwrap(),
13056                window,
13057                cx,
13058            )
13059        })
13060        .await
13061        .unwrap();
13062    assert_eq!(
13063        follower_1.update(cx, |editor, cx| editor.text(cx)),
13064        leader.update(cx, |editor, cx| editor.text(cx))
13065    );
13066    update_message.borrow_mut().take();
13067
13068    // Start following separately after it already has excerpts.
13069    let mut state_message =
13070        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13071    let workspace_entity = workspace.root(cx).unwrap();
13072    let follower_2 = cx
13073        .update_window(*workspace.deref(), |_, window, cx| {
13074            Editor::from_state_proto(
13075                workspace_entity,
13076                ViewId {
13077                    creator: CollaboratorId::PeerId(PeerId::default()),
13078                    id: 0,
13079                },
13080                &mut state_message,
13081                window,
13082                cx,
13083            )
13084        })
13085        .unwrap()
13086        .unwrap()
13087        .await
13088        .unwrap();
13089    assert_eq!(
13090        follower_2.update(cx, |editor, cx| editor.text(cx)),
13091        leader.update(cx, |editor, cx| editor.text(cx))
13092    );
13093
13094    // Remove some excerpts.
13095    leader.update(cx, |leader, cx| {
13096        leader.buffer.update(cx, |multibuffer, cx| {
13097            let excerpt_ids = multibuffer.excerpt_ids();
13098            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13099            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13100        });
13101    });
13102
13103    // Apply the update of removing the excerpts.
13104    follower_1
13105        .update_in(cx, |follower, window, cx| {
13106            follower.apply_update_proto(
13107                &project,
13108                update_message.borrow().clone().unwrap(),
13109                window,
13110                cx,
13111            )
13112        })
13113        .await
13114        .unwrap();
13115    follower_2
13116        .update_in(cx, |follower, window, cx| {
13117            follower.apply_update_proto(
13118                &project,
13119                update_message.borrow().clone().unwrap(),
13120                window,
13121                cx,
13122            )
13123        })
13124        .await
13125        .unwrap();
13126    update_message.borrow_mut().take();
13127    assert_eq!(
13128        follower_1.update(cx, |editor, cx| editor.text(cx)),
13129        leader.update(cx, |editor, cx| editor.text(cx))
13130    );
13131}
13132
13133#[gpui::test]
13134async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13135    init_test(cx, |_| {});
13136
13137    let mut cx = EditorTestContext::new(cx).await;
13138    let lsp_store =
13139        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13140
13141    cx.set_state(indoc! {"
13142        ˇfn func(abc def: i32) -> u32 {
13143        }
13144    "});
13145
13146    cx.update(|_, cx| {
13147        lsp_store.update(cx, |lsp_store, cx| {
13148            lsp_store
13149                .update_diagnostics(
13150                    LanguageServerId(0),
13151                    lsp::PublishDiagnosticsParams {
13152                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13153                        version: None,
13154                        diagnostics: vec![
13155                            lsp::Diagnostic {
13156                                range: lsp::Range::new(
13157                                    lsp::Position::new(0, 11),
13158                                    lsp::Position::new(0, 12),
13159                                ),
13160                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13161                                ..Default::default()
13162                            },
13163                            lsp::Diagnostic {
13164                                range: lsp::Range::new(
13165                                    lsp::Position::new(0, 12),
13166                                    lsp::Position::new(0, 15),
13167                                ),
13168                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13169                                ..Default::default()
13170                            },
13171                            lsp::Diagnostic {
13172                                range: lsp::Range::new(
13173                                    lsp::Position::new(0, 25),
13174                                    lsp::Position::new(0, 28),
13175                                ),
13176                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13177                                ..Default::default()
13178                            },
13179                        ],
13180                    },
13181                    &[],
13182                    cx,
13183                )
13184                .unwrap()
13185        });
13186    });
13187
13188    executor.run_until_parked();
13189
13190    cx.update_editor(|editor, window, cx| {
13191        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13192    });
13193
13194    cx.assert_editor_state(indoc! {"
13195        fn func(abc def: i32) -> ˇu32 {
13196        }
13197    "});
13198
13199    cx.update_editor(|editor, window, cx| {
13200        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13201    });
13202
13203    cx.assert_editor_state(indoc! {"
13204        fn func(abc ˇdef: i32) -> u32 {
13205        }
13206    "});
13207
13208    cx.update_editor(|editor, window, cx| {
13209        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13210    });
13211
13212    cx.assert_editor_state(indoc! {"
13213        fn func(abcˇ def: i32) -> u32 {
13214        }
13215    "});
13216
13217    cx.update_editor(|editor, window, cx| {
13218        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13219    });
13220
13221    cx.assert_editor_state(indoc! {"
13222        fn func(abc def: i32) -> ˇu32 {
13223        }
13224    "});
13225}
13226
13227#[gpui::test]
13228async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13229    init_test(cx, |_| {});
13230
13231    let mut cx = EditorTestContext::new(cx).await;
13232
13233    let diff_base = r#"
13234        use some::mod;
13235
13236        const A: u32 = 42;
13237
13238        fn main() {
13239            println!("hello");
13240
13241            println!("world");
13242        }
13243        "#
13244    .unindent();
13245
13246    // Edits are modified, removed, modified, added
13247    cx.set_state(
13248        &r#"
13249        use some::modified;
13250
13251        ˇ
13252        fn main() {
13253            println!("hello there");
13254
13255            println!("around the");
13256            println!("world");
13257        }
13258        "#
13259        .unindent(),
13260    );
13261
13262    cx.set_head_text(&diff_base);
13263    executor.run_until_parked();
13264
13265    cx.update_editor(|editor, window, cx| {
13266        //Wrap around the bottom of the buffer
13267        for _ in 0..3 {
13268            editor.go_to_next_hunk(&GoToHunk, window, cx);
13269        }
13270    });
13271
13272    cx.assert_editor_state(
13273        &r#"
13274        ˇuse some::modified;
13275
13276
13277        fn main() {
13278            println!("hello there");
13279
13280            println!("around the");
13281            println!("world");
13282        }
13283        "#
13284        .unindent(),
13285    );
13286
13287    cx.update_editor(|editor, window, cx| {
13288        //Wrap around the top of the buffer
13289        for _ in 0..2 {
13290            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13291        }
13292    });
13293
13294    cx.assert_editor_state(
13295        &r#"
13296        use some::modified;
13297
13298
13299        fn main() {
13300        ˇ    println!("hello there");
13301
13302            println!("around the");
13303            println!("world");
13304        }
13305        "#
13306        .unindent(),
13307    );
13308
13309    cx.update_editor(|editor, window, cx| {
13310        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13311    });
13312
13313    cx.assert_editor_state(
13314        &r#"
13315        use some::modified;
13316
13317        ˇ
13318        fn main() {
13319            println!("hello there");
13320
13321            println!("around the");
13322            println!("world");
13323        }
13324        "#
13325        .unindent(),
13326    );
13327
13328    cx.update_editor(|editor, window, cx| {
13329        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13330    });
13331
13332    cx.assert_editor_state(
13333        &r#"
13334        ˇuse some::modified;
13335
13336
13337        fn main() {
13338            println!("hello there");
13339
13340            println!("around the");
13341            println!("world");
13342        }
13343        "#
13344        .unindent(),
13345    );
13346
13347    cx.update_editor(|editor, window, cx| {
13348        for _ in 0..2 {
13349            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13350        }
13351    });
13352
13353    cx.assert_editor_state(
13354        &r#"
13355        use some::modified;
13356
13357
13358        fn main() {
13359        ˇ    println!("hello there");
13360
13361            println!("around the");
13362            println!("world");
13363        }
13364        "#
13365        .unindent(),
13366    );
13367
13368    cx.update_editor(|editor, window, cx| {
13369        editor.fold(&Fold, window, cx);
13370    });
13371
13372    cx.update_editor(|editor, window, cx| {
13373        editor.go_to_next_hunk(&GoToHunk, window, cx);
13374    });
13375
13376    cx.assert_editor_state(
13377        &r#"
13378        ˇuse some::modified;
13379
13380
13381        fn main() {
13382            println!("hello there");
13383
13384            println!("around the");
13385            println!("world");
13386        }
13387        "#
13388        .unindent(),
13389    );
13390}
13391
13392#[test]
13393fn test_split_words() {
13394    fn split(text: &str) -> Vec<&str> {
13395        split_words(text).collect()
13396    }
13397
13398    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13399    assert_eq!(split("hello_world"), &["hello_", "world"]);
13400    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13401    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13402    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13403    assert_eq!(split("helloworld"), &["helloworld"]);
13404
13405    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13406}
13407
13408#[gpui::test]
13409async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13410    init_test(cx, |_| {});
13411
13412    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13413    let mut assert = |before, after| {
13414        let _state_context = cx.set_state(before);
13415        cx.run_until_parked();
13416        cx.update_editor(|editor, window, cx| {
13417            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13418        });
13419        cx.run_until_parked();
13420        cx.assert_editor_state(after);
13421    };
13422
13423    // Outside bracket jumps to outside of matching bracket
13424    assert("console.logˇ(var);", "console.log(var)ˇ;");
13425    assert("console.log(var)ˇ;", "console.logˇ(var);");
13426
13427    // Inside bracket jumps to inside of matching bracket
13428    assert("console.log(ˇvar);", "console.log(varˇ);");
13429    assert("console.log(varˇ);", "console.log(ˇvar);");
13430
13431    // When outside a bracket and inside, favor jumping to the inside bracket
13432    assert(
13433        "console.log('foo', [1, 2, 3]ˇ);",
13434        "console.log(ˇ'foo', [1, 2, 3]);",
13435    );
13436    assert(
13437        "console.log(ˇ'foo', [1, 2, 3]);",
13438        "console.log('foo', [1, 2, 3]ˇ);",
13439    );
13440
13441    // Bias forward if two options are equally likely
13442    assert(
13443        "let result = curried_fun()ˇ();",
13444        "let result = curried_fun()()ˇ;",
13445    );
13446
13447    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13448    assert(
13449        indoc! {"
13450            function test() {
13451                console.log('test')ˇ
13452            }"},
13453        indoc! {"
13454            function test() {
13455                console.logˇ('test')
13456            }"},
13457    );
13458}
13459
13460#[gpui::test]
13461async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13462    init_test(cx, |_| {});
13463
13464    let fs = FakeFs::new(cx.executor());
13465    fs.insert_tree(
13466        path!("/a"),
13467        json!({
13468            "main.rs": "fn main() { let a = 5; }",
13469            "other.rs": "// Test file",
13470        }),
13471    )
13472    .await;
13473    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13474
13475    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13476    language_registry.add(Arc::new(Language::new(
13477        LanguageConfig {
13478            name: "Rust".into(),
13479            matcher: LanguageMatcher {
13480                path_suffixes: vec!["rs".to_string()],
13481                ..Default::default()
13482            },
13483            brackets: BracketPairConfig {
13484                pairs: vec![BracketPair {
13485                    start: "{".to_string(),
13486                    end: "}".to_string(),
13487                    close: true,
13488                    surround: true,
13489                    newline: true,
13490                }],
13491                disabled_scopes_by_bracket_ix: Vec::new(),
13492            },
13493            ..Default::default()
13494        },
13495        Some(tree_sitter_rust::LANGUAGE.into()),
13496    )));
13497    let mut fake_servers = language_registry.register_fake_lsp(
13498        "Rust",
13499        FakeLspAdapter {
13500            capabilities: lsp::ServerCapabilities {
13501                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13502                    first_trigger_character: "{".to_string(),
13503                    more_trigger_character: None,
13504                }),
13505                ..Default::default()
13506            },
13507            ..Default::default()
13508        },
13509    );
13510
13511    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13512
13513    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13514
13515    let worktree_id = workspace
13516        .update(cx, |workspace, _, cx| {
13517            workspace.project().update(cx, |project, cx| {
13518                project.worktrees(cx).next().unwrap().read(cx).id()
13519            })
13520        })
13521        .unwrap();
13522
13523    let buffer = project
13524        .update(cx, |project, cx| {
13525            project.open_local_buffer(path!("/a/main.rs"), cx)
13526        })
13527        .await
13528        .unwrap();
13529    let editor_handle = workspace
13530        .update(cx, |workspace, window, cx| {
13531            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13532        })
13533        .unwrap()
13534        .await
13535        .unwrap()
13536        .downcast::<Editor>()
13537        .unwrap();
13538
13539    cx.executor().start_waiting();
13540    let fake_server = fake_servers.next().await.unwrap();
13541
13542    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13543        |params, _| async move {
13544            assert_eq!(
13545                params.text_document_position.text_document.uri,
13546                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13547            );
13548            assert_eq!(
13549                params.text_document_position.position,
13550                lsp::Position::new(0, 21),
13551            );
13552
13553            Ok(Some(vec![lsp::TextEdit {
13554                new_text: "]".to_string(),
13555                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13556            }]))
13557        },
13558    );
13559
13560    editor_handle.update_in(cx, |editor, window, cx| {
13561        window.focus(&editor.focus_handle(cx));
13562        editor.change_selections(None, window, cx, |s| {
13563            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13564        });
13565        editor.handle_input("{", window, cx);
13566    });
13567
13568    cx.executor().run_until_parked();
13569
13570    buffer.update(cx, |buffer, _| {
13571        assert_eq!(
13572            buffer.text(),
13573            "fn main() { let a = {5}; }",
13574            "No extra braces from on type formatting should appear in the buffer"
13575        )
13576    });
13577}
13578
13579#[gpui::test]
13580async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13581    init_test(cx, |_| {});
13582
13583    let fs = FakeFs::new(cx.executor());
13584    fs.insert_tree(
13585        path!("/a"),
13586        json!({
13587            "main.rs": "fn main() { let a = 5; }",
13588            "other.rs": "// Test file",
13589        }),
13590    )
13591    .await;
13592
13593    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13594
13595    let server_restarts = Arc::new(AtomicUsize::new(0));
13596    let closure_restarts = Arc::clone(&server_restarts);
13597    let language_server_name = "test language server";
13598    let language_name: LanguageName = "Rust".into();
13599
13600    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13601    language_registry.add(Arc::new(Language::new(
13602        LanguageConfig {
13603            name: language_name.clone(),
13604            matcher: LanguageMatcher {
13605                path_suffixes: vec!["rs".to_string()],
13606                ..Default::default()
13607            },
13608            ..Default::default()
13609        },
13610        Some(tree_sitter_rust::LANGUAGE.into()),
13611    )));
13612    let mut fake_servers = language_registry.register_fake_lsp(
13613        "Rust",
13614        FakeLspAdapter {
13615            name: language_server_name,
13616            initialization_options: Some(json!({
13617                "testOptionValue": true
13618            })),
13619            initializer: Some(Box::new(move |fake_server| {
13620                let task_restarts = Arc::clone(&closure_restarts);
13621                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13622                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13623                    futures::future::ready(Ok(()))
13624                });
13625            })),
13626            ..Default::default()
13627        },
13628    );
13629
13630    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13631    let _buffer = project
13632        .update(cx, |project, cx| {
13633            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13634        })
13635        .await
13636        .unwrap();
13637    let _fake_server = fake_servers.next().await.unwrap();
13638    update_test_language_settings(cx, |language_settings| {
13639        language_settings.languages.insert(
13640            language_name.clone(),
13641            LanguageSettingsContent {
13642                tab_size: NonZeroU32::new(8),
13643                ..Default::default()
13644            },
13645        );
13646    });
13647    cx.executor().run_until_parked();
13648    assert_eq!(
13649        server_restarts.load(atomic::Ordering::Acquire),
13650        0,
13651        "Should not restart LSP server on an unrelated change"
13652    );
13653
13654    update_test_project_settings(cx, |project_settings| {
13655        project_settings.lsp.insert(
13656            "Some other server name".into(),
13657            LspSettings {
13658                binary: None,
13659                settings: None,
13660                initialization_options: Some(json!({
13661                    "some other init value": false
13662                })),
13663                enable_lsp_tasks: false,
13664            },
13665        );
13666    });
13667    cx.executor().run_until_parked();
13668    assert_eq!(
13669        server_restarts.load(atomic::Ordering::Acquire),
13670        0,
13671        "Should not restart LSP server on an unrelated LSP settings change"
13672    );
13673
13674    update_test_project_settings(cx, |project_settings| {
13675        project_settings.lsp.insert(
13676            language_server_name.into(),
13677            LspSettings {
13678                binary: None,
13679                settings: None,
13680                initialization_options: Some(json!({
13681                    "anotherInitValue": false
13682                })),
13683                enable_lsp_tasks: false,
13684            },
13685        );
13686    });
13687    cx.executor().run_until_parked();
13688    assert_eq!(
13689        server_restarts.load(atomic::Ordering::Acquire),
13690        1,
13691        "Should restart LSP server on a related LSP settings change"
13692    );
13693
13694    update_test_project_settings(cx, |project_settings| {
13695        project_settings.lsp.insert(
13696            language_server_name.into(),
13697            LspSettings {
13698                binary: None,
13699                settings: None,
13700                initialization_options: Some(json!({
13701                    "anotherInitValue": false
13702                })),
13703                enable_lsp_tasks: false,
13704            },
13705        );
13706    });
13707    cx.executor().run_until_parked();
13708    assert_eq!(
13709        server_restarts.load(atomic::Ordering::Acquire),
13710        1,
13711        "Should not restart LSP server on a related LSP settings change that is the same"
13712    );
13713
13714    update_test_project_settings(cx, |project_settings| {
13715        project_settings.lsp.insert(
13716            language_server_name.into(),
13717            LspSettings {
13718                binary: None,
13719                settings: None,
13720                initialization_options: None,
13721                enable_lsp_tasks: false,
13722            },
13723        );
13724    });
13725    cx.executor().run_until_parked();
13726    assert_eq!(
13727        server_restarts.load(atomic::Ordering::Acquire),
13728        2,
13729        "Should restart LSP server on another related LSP settings change"
13730    );
13731}
13732
13733#[gpui::test]
13734async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13735    init_test(cx, |_| {});
13736
13737    let mut cx = EditorLspTestContext::new_rust(
13738        lsp::ServerCapabilities {
13739            completion_provider: Some(lsp::CompletionOptions {
13740                trigger_characters: Some(vec![".".to_string()]),
13741                resolve_provider: Some(true),
13742                ..Default::default()
13743            }),
13744            ..Default::default()
13745        },
13746        cx,
13747    )
13748    .await;
13749
13750    cx.set_state("fn main() { let a = 2ˇ; }");
13751    cx.simulate_keystroke(".");
13752    let completion_item = lsp::CompletionItem {
13753        label: "some".into(),
13754        kind: Some(lsp::CompletionItemKind::SNIPPET),
13755        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13756        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13757            kind: lsp::MarkupKind::Markdown,
13758            value: "```rust\nSome(2)\n```".to_string(),
13759        })),
13760        deprecated: Some(false),
13761        sort_text: Some("fffffff2".to_string()),
13762        filter_text: Some("some".to_string()),
13763        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13764        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13765            range: lsp::Range {
13766                start: lsp::Position {
13767                    line: 0,
13768                    character: 22,
13769                },
13770                end: lsp::Position {
13771                    line: 0,
13772                    character: 22,
13773                },
13774            },
13775            new_text: "Some(2)".to_string(),
13776        })),
13777        additional_text_edits: Some(vec![lsp::TextEdit {
13778            range: lsp::Range {
13779                start: lsp::Position {
13780                    line: 0,
13781                    character: 20,
13782                },
13783                end: lsp::Position {
13784                    line: 0,
13785                    character: 22,
13786                },
13787            },
13788            new_text: "".to_string(),
13789        }]),
13790        ..Default::default()
13791    };
13792
13793    let closure_completion_item = completion_item.clone();
13794    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13795        let task_completion_item = closure_completion_item.clone();
13796        async move {
13797            Ok(Some(lsp::CompletionResponse::Array(vec![
13798                task_completion_item,
13799            ])))
13800        }
13801    });
13802
13803    request.next().await;
13804
13805    cx.condition(|editor, _| editor.context_menu_visible())
13806        .await;
13807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13808        editor
13809            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13810            .unwrap()
13811    });
13812    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13813
13814    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13815        let task_completion_item = completion_item.clone();
13816        async move { Ok(task_completion_item) }
13817    })
13818    .next()
13819    .await
13820    .unwrap();
13821    apply_additional_edits.await.unwrap();
13822    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13823}
13824
13825#[gpui::test]
13826async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13827    init_test(cx, |_| {});
13828
13829    let mut cx = EditorLspTestContext::new_rust(
13830        lsp::ServerCapabilities {
13831            completion_provider: Some(lsp::CompletionOptions {
13832                trigger_characters: Some(vec![".".to_string()]),
13833                resolve_provider: Some(true),
13834                ..Default::default()
13835            }),
13836            ..Default::default()
13837        },
13838        cx,
13839    )
13840    .await;
13841
13842    cx.set_state("fn main() { let a = 2ˇ; }");
13843    cx.simulate_keystroke(".");
13844
13845    let item1 = lsp::CompletionItem {
13846        label: "method id()".to_string(),
13847        filter_text: Some("id".to_string()),
13848        detail: None,
13849        documentation: None,
13850        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13851            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13852            new_text: ".id".to_string(),
13853        })),
13854        ..lsp::CompletionItem::default()
13855    };
13856
13857    let item2 = lsp::CompletionItem {
13858        label: "other".to_string(),
13859        filter_text: Some("other".to_string()),
13860        detail: None,
13861        documentation: None,
13862        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13863            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13864            new_text: ".other".to_string(),
13865        })),
13866        ..lsp::CompletionItem::default()
13867    };
13868
13869    let item1 = item1.clone();
13870    cx.set_request_handler::<lsp::request::Completion, _, _>({
13871        let item1 = item1.clone();
13872        move |_, _, _| {
13873            let item1 = item1.clone();
13874            let item2 = item2.clone();
13875            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13876        }
13877    })
13878    .next()
13879    .await;
13880
13881    cx.condition(|editor, _| editor.context_menu_visible())
13882        .await;
13883    cx.update_editor(|editor, _, _| {
13884        let context_menu = editor.context_menu.borrow_mut();
13885        let context_menu = context_menu
13886            .as_ref()
13887            .expect("Should have the context menu deployed");
13888        match context_menu {
13889            CodeContextMenu::Completions(completions_menu) => {
13890                let completions = completions_menu.completions.borrow_mut();
13891                assert_eq!(
13892                    completions
13893                        .iter()
13894                        .map(|completion| &completion.label.text)
13895                        .collect::<Vec<_>>(),
13896                    vec!["method id()", "other"]
13897                )
13898            }
13899            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13900        }
13901    });
13902
13903    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13904        let item1 = item1.clone();
13905        move |_, item_to_resolve, _| {
13906            let item1 = item1.clone();
13907            async move {
13908                if item1 == item_to_resolve {
13909                    Ok(lsp::CompletionItem {
13910                        label: "method id()".to_string(),
13911                        filter_text: Some("id".to_string()),
13912                        detail: Some("Now resolved!".to_string()),
13913                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13914                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13915                            range: lsp::Range::new(
13916                                lsp::Position::new(0, 22),
13917                                lsp::Position::new(0, 22),
13918                            ),
13919                            new_text: ".id".to_string(),
13920                        })),
13921                        ..lsp::CompletionItem::default()
13922                    })
13923                } else {
13924                    Ok(item_to_resolve)
13925                }
13926            }
13927        }
13928    })
13929    .next()
13930    .await
13931    .unwrap();
13932    cx.run_until_parked();
13933
13934    cx.update_editor(|editor, window, cx| {
13935        editor.context_menu_next(&Default::default(), window, cx);
13936    });
13937
13938    cx.update_editor(|editor, _, _| {
13939        let context_menu = editor.context_menu.borrow_mut();
13940        let context_menu = context_menu
13941            .as_ref()
13942            .expect("Should have the context menu deployed");
13943        match context_menu {
13944            CodeContextMenu::Completions(completions_menu) => {
13945                let completions = completions_menu.completions.borrow_mut();
13946                assert_eq!(
13947                    completions
13948                        .iter()
13949                        .map(|completion| &completion.label.text)
13950                        .collect::<Vec<_>>(),
13951                    vec!["method id() Now resolved!", "other"],
13952                    "Should update first completion label, but not second as the filter text did not match."
13953                );
13954            }
13955            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13956        }
13957    });
13958}
13959
13960#[gpui::test]
13961async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13962    init_test(cx, |_| {});
13963
13964    let mut cx = EditorLspTestContext::new_rust(
13965        lsp::ServerCapabilities {
13966            completion_provider: Some(lsp::CompletionOptions {
13967                trigger_characters: Some(vec![".".to_string()]),
13968                resolve_provider: Some(true),
13969                ..Default::default()
13970            }),
13971            ..Default::default()
13972        },
13973        cx,
13974    )
13975    .await;
13976
13977    cx.set_state("fn main() { let a = 2ˇ; }");
13978    cx.simulate_keystroke(".");
13979
13980    let unresolved_item_1 = lsp::CompletionItem {
13981        label: "id".to_string(),
13982        filter_text: Some("id".to_string()),
13983        detail: None,
13984        documentation: None,
13985        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13986            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13987            new_text: ".id".to_string(),
13988        })),
13989        ..lsp::CompletionItem::default()
13990    };
13991    let resolved_item_1 = lsp::CompletionItem {
13992        additional_text_edits: Some(vec![lsp::TextEdit {
13993            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
13994            new_text: "!!".to_string(),
13995        }]),
13996        ..unresolved_item_1.clone()
13997    };
13998    let unresolved_item_2 = lsp::CompletionItem {
13999        label: "other".to_string(),
14000        filter_text: Some("other".to_string()),
14001        detail: None,
14002        documentation: None,
14003        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14004            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14005            new_text: ".other".to_string(),
14006        })),
14007        ..lsp::CompletionItem::default()
14008    };
14009    let resolved_item_2 = lsp::CompletionItem {
14010        additional_text_edits: Some(vec![lsp::TextEdit {
14011            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14012            new_text: "??".to_string(),
14013        }]),
14014        ..unresolved_item_2.clone()
14015    };
14016
14017    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14018    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14019    cx.lsp
14020        .server
14021        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14022            let unresolved_item_1 = unresolved_item_1.clone();
14023            let resolved_item_1 = resolved_item_1.clone();
14024            let unresolved_item_2 = unresolved_item_2.clone();
14025            let resolved_item_2 = resolved_item_2.clone();
14026            let resolve_requests_1 = resolve_requests_1.clone();
14027            let resolve_requests_2 = resolve_requests_2.clone();
14028            move |unresolved_request, _| {
14029                let unresolved_item_1 = unresolved_item_1.clone();
14030                let resolved_item_1 = resolved_item_1.clone();
14031                let unresolved_item_2 = unresolved_item_2.clone();
14032                let resolved_item_2 = resolved_item_2.clone();
14033                let resolve_requests_1 = resolve_requests_1.clone();
14034                let resolve_requests_2 = resolve_requests_2.clone();
14035                async move {
14036                    if unresolved_request == unresolved_item_1 {
14037                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14038                        Ok(resolved_item_1.clone())
14039                    } else if unresolved_request == unresolved_item_2 {
14040                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14041                        Ok(resolved_item_2.clone())
14042                    } else {
14043                        panic!("Unexpected completion item {unresolved_request:?}")
14044                    }
14045                }
14046            }
14047        })
14048        .detach();
14049
14050    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14051        let unresolved_item_1 = unresolved_item_1.clone();
14052        let unresolved_item_2 = unresolved_item_2.clone();
14053        async move {
14054            Ok(Some(lsp::CompletionResponse::Array(vec![
14055                unresolved_item_1,
14056                unresolved_item_2,
14057            ])))
14058        }
14059    })
14060    .next()
14061    .await;
14062
14063    cx.condition(|editor, _| editor.context_menu_visible())
14064        .await;
14065    cx.update_editor(|editor, _, _| {
14066        let context_menu = editor.context_menu.borrow_mut();
14067        let context_menu = context_menu
14068            .as_ref()
14069            .expect("Should have the context menu deployed");
14070        match context_menu {
14071            CodeContextMenu::Completions(completions_menu) => {
14072                let completions = completions_menu.completions.borrow_mut();
14073                assert_eq!(
14074                    completions
14075                        .iter()
14076                        .map(|completion| &completion.label.text)
14077                        .collect::<Vec<_>>(),
14078                    vec!["id", "other"]
14079                )
14080            }
14081            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14082        }
14083    });
14084    cx.run_until_parked();
14085
14086    cx.update_editor(|editor, window, cx| {
14087        editor.context_menu_next(&ContextMenuNext, window, cx);
14088    });
14089    cx.run_until_parked();
14090    cx.update_editor(|editor, window, cx| {
14091        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14092    });
14093    cx.run_until_parked();
14094    cx.update_editor(|editor, window, cx| {
14095        editor.context_menu_next(&ContextMenuNext, window, cx);
14096    });
14097    cx.run_until_parked();
14098    cx.update_editor(|editor, window, cx| {
14099        editor
14100            .compose_completion(&ComposeCompletion::default(), window, cx)
14101            .expect("No task returned")
14102    })
14103    .await
14104    .expect("Completion failed");
14105    cx.run_until_parked();
14106
14107    cx.update_editor(|editor, _, cx| {
14108        assert_eq!(
14109            resolve_requests_1.load(atomic::Ordering::Acquire),
14110            1,
14111            "Should always resolve once despite multiple selections"
14112        );
14113        assert_eq!(
14114            resolve_requests_2.load(atomic::Ordering::Acquire),
14115            1,
14116            "Should always resolve once after multiple selections and applying the completion"
14117        );
14118        assert_eq!(
14119            editor.text(cx),
14120            "fn main() { let a = ??.other; }",
14121            "Should use resolved data when applying the completion"
14122        );
14123    });
14124}
14125
14126#[gpui::test]
14127async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14128    init_test(cx, |_| {});
14129
14130    let item_0 = lsp::CompletionItem {
14131        label: "abs".into(),
14132        insert_text: Some("abs".into()),
14133        data: Some(json!({ "very": "special"})),
14134        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14135        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14136            lsp::InsertReplaceEdit {
14137                new_text: "abs".to_string(),
14138                insert: lsp::Range::default(),
14139                replace: lsp::Range::default(),
14140            },
14141        )),
14142        ..lsp::CompletionItem::default()
14143    };
14144    let items = iter::once(item_0.clone())
14145        .chain((11..51).map(|i| lsp::CompletionItem {
14146            label: format!("item_{}", i),
14147            insert_text: Some(format!("item_{}", i)),
14148            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14149            ..lsp::CompletionItem::default()
14150        }))
14151        .collect::<Vec<_>>();
14152
14153    let default_commit_characters = vec!["?".to_string()];
14154    let default_data = json!({ "default": "data"});
14155    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14156    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14157    let default_edit_range = lsp::Range {
14158        start: lsp::Position {
14159            line: 0,
14160            character: 5,
14161        },
14162        end: lsp::Position {
14163            line: 0,
14164            character: 5,
14165        },
14166    };
14167
14168    let mut cx = EditorLspTestContext::new_rust(
14169        lsp::ServerCapabilities {
14170            completion_provider: Some(lsp::CompletionOptions {
14171                trigger_characters: Some(vec![".".to_string()]),
14172                resolve_provider: Some(true),
14173                ..Default::default()
14174            }),
14175            ..Default::default()
14176        },
14177        cx,
14178    )
14179    .await;
14180
14181    cx.set_state("fn main() { let a = 2ˇ; }");
14182    cx.simulate_keystroke(".");
14183
14184    let completion_data = default_data.clone();
14185    let completion_characters = default_commit_characters.clone();
14186    let completion_items = items.clone();
14187    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14188        let default_data = completion_data.clone();
14189        let default_commit_characters = completion_characters.clone();
14190        let items = completion_items.clone();
14191        async move {
14192            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14193                items,
14194                item_defaults: Some(lsp::CompletionListItemDefaults {
14195                    data: Some(default_data.clone()),
14196                    commit_characters: Some(default_commit_characters.clone()),
14197                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14198                        default_edit_range,
14199                    )),
14200                    insert_text_format: Some(default_insert_text_format),
14201                    insert_text_mode: Some(default_insert_text_mode),
14202                }),
14203                ..lsp::CompletionList::default()
14204            })))
14205        }
14206    })
14207    .next()
14208    .await;
14209
14210    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14211    cx.lsp
14212        .server
14213        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14214            let closure_resolved_items = resolved_items.clone();
14215            move |item_to_resolve, _| {
14216                let closure_resolved_items = closure_resolved_items.clone();
14217                async move {
14218                    closure_resolved_items.lock().push(item_to_resolve.clone());
14219                    Ok(item_to_resolve)
14220                }
14221            }
14222        })
14223        .detach();
14224
14225    cx.condition(|editor, _| editor.context_menu_visible())
14226        .await;
14227    cx.run_until_parked();
14228    cx.update_editor(|editor, _, _| {
14229        let menu = editor.context_menu.borrow_mut();
14230        match menu.as_ref().expect("should have the completions menu") {
14231            CodeContextMenu::Completions(completions_menu) => {
14232                assert_eq!(
14233                    completions_menu
14234                        .entries
14235                        .borrow()
14236                        .iter()
14237                        .map(|mat| mat.string.clone())
14238                        .collect::<Vec<String>>(),
14239                    items
14240                        .iter()
14241                        .map(|completion| completion.label.clone())
14242                        .collect::<Vec<String>>()
14243                );
14244            }
14245            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14246        }
14247    });
14248    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14249    // with 4 from the end.
14250    assert_eq!(
14251        *resolved_items.lock(),
14252        [&items[0..16], &items[items.len() - 4..items.len()]]
14253            .concat()
14254            .iter()
14255            .cloned()
14256            .map(|mut item| {
14257                if item.data.is_none() {
14258                    item.data = Some(default_data.clone());
14259                }
14260                item
14261            })
14262            .collect::<Vec<lsp::CompletionItem>>(),
14263        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14264    );
14265    resolved_items.lock().clear();
14266
14267    cx.update_editor(|editor, window, cx| {
14268        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14269    });
14270    cx.run_until_parked();
14271    // Completions that have already been resolved are skipped.
14272    assert_eq!(
14273        *resolved_items.lock(),
14274        items[items.len() - 16..items.len() - 4]
14275            .iter()
14276            .cloned()
14277            .map(|mut item| {
14278                if item.data.is_none() {
14279                    item.data = Some(default_data.clone());
14280                }
14281                item
14282            })
14283            .collect::<Vec<lsp::CompletionItem>>()
14284    );
14285    resolved_items.lock().clear();
14286}
14287
14288#[gpui::test]
14289async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14290    init_test(cx, |_| {});
14291
14292    let mut cx = EditorLspTestContext::new(
14293        Language::new(
14294            LanguageConfig {
14295                matcher: LanguageMatcher {
14296                    path_suffixes: vec!["jsx".into()],
14297                    ..Default::default()
14298                },
14299                overrides: [(
14300                    "element".into(),
14301                    LanguageConfigOverride {
14302                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14303                        ..Default::default()
14304                    },
14305                )]
14306                .into_iter()
14307                .collect(),
14308                ..Default::default()
14309            },
14310            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14311        )
14312        .with_override_query("(jsx_self_closing_element) @element")
14313        .unwrap(),
14314        lsp::ServerCapabilities {
14315            completion_provider: Some(lsp::CompletionOptions {
14316                trigger_characters: Some(vec![":".to_string()]),
14317                ..Default::default()
14318            }),
14319            ..Default::default()
14320        },
14321        cx,
14322    )
14323    .await;
14324
14325    cx.lsp
14326        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14327            Ok(Some(lsp::CompletionResponse::Array(vec![
14328                lsp::CompletionItem {
14329                    label: "bg-blue".into(),
14330                    ..Default::default()
14331                },
14332                lsp::CompletionItem {
14333                    label: "bg-red".into(),
14334                    ..Default::default()
14335                },
14336                lsp::CompletionItem {
14337                    label: "bg-yellow".into(),
14338                    ..Default::default()
14339                },
14340            ])))
14341        });
14342
14343    cx.set_state(r#"<p class="bgˇ" />"#);
14344
14345    // Trigger completion when typing a dash, because the dash is an extra
14346    // word character in the 'element' scope, which contains the cursor.
14347    cx.simulate_keystroke("-");
14348    cx.executor().run_until_parked();
14349    cx.update_editor(|editor, _, _| {
14350        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14351        {
14352            assert_eq!(
14353                completion_menu_entries(&menu),
14354                &["bg-red", "bg-blue", "bg-yellow"]
14355            );
14356        } else {
14357            panic!("expected completion menu to be open");
14358        }
14359    });
14360
14361    cx.simulate_keystroke("l");
14362    cx.executor().run_until_parked();
14363    cx.update_editor(|editor, _, _| {
14364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14365        {
14366            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14367        } else {
14368            panic!("expected completion menu to be open");
14369        }
14370    });
14371
14372    // When filtering completions, consider the character after the '-' to
14373    // be the start of a subword.
14374    cx.set_state(r#"<p class="yelˇ" />"#);
14375    cx.simulate_keystroke("l");
14376    cx.executor().run_until_parked();
14377    cx.update_editor(|editor, _, _| {
14378        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14379        {
14380            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14381        } else {
14382            panic!("expected completion menu to be open");
14383        }
14384    });
14385}
14386
14387fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14388    let entries = menu.entries.borrow();
14389    entries.iter().map(|mat| mat.string.clone()).collect()
14390}
14391
14392#[gpui::test]
14393async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14394    init_test(cx, |settings| {
14395        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14396            FormatterList(vec![Formatter::Prettier].into()),
14397        ))
14398    });
14399
14400    let fs = FakeFs::new(cx.executor());
14401    fs.insert_file(path!("/file.ts"), Default::default()).await;
14402
14403    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14404    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14405
14406    language_registry.add(Arc::new(Language::new(
14407        LanguageConfig {
14408            name: "TypeScript".into(),
14409            matcher: LanguageMatcher {
14410                path_suffixes: vec!["ts".to_string()],
14411                ..Default::default()
14412            },
14413            ..Default::default()
14414        },
14415        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14416    )));
14417    update_test_language_settings(cx, |settings| {
14418        settings.defaults.prettier = Some(PrettierSettings {
14419            allowed: true,
14420            ..PrettierSettings::default()
14421        });
14422    });
14423
14424    let test_plugin = "test_plugin";
14425    let _ = language_registry.register_fake_lsp(
14426        "TypeScript",
14427        FakeLspAdapter {
14428            prettier_plugins: vec![test_plugin],
14429            ..Default::default()
14430        },
14431    );
14432
14433    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14434    let buffer = project
14435        .update(cx, |project, cx| {
14436            project.open_local_buffer(path!("/file.ts"), cx)
14437        })
14438        .await
14439        .unwrap();
14440
14441    let buffer_text = "one\ntwo\nthree\n";
14442    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14443    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14444    editor.update_in(cx, |editor, window, cx| {
14445        editor.set_text(buffer_text, window, cx)
14446    });
14447
14448    editor
14449        .update_in(cx, |editor, window, cx| {
14450            editor.perform_format(
14451                project.clone(),
14452                FormatTrigger::Manual,
14453                FormatTarget::Buffers,
14454                window,
14455                cx,
14456            )
14457        })
14458        .unwrap()
14459        .await;
14460    assert_eq!(
14461        editor.update(cx, |editor, cx| editor.text(cx)),
14462        buffer_text.to_string() + prettier_format_suffix,
14463        "Test prettier formatting was not applied to the original buffer text",
14464    );
14465
14466    update_test_language_settings(cx, |settings| {
14467        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14468    });
14469    let format = editor.update_in(cx, |editor, window, cx| {
14470        editor.perform_format(
14471            project.clone(),
14472            FormatTrigger::Manual,
14473            FormatTarget::Buffers,
14474            window,
14475            cx,
14476        )
14477    });
14478    format.await.unwrap();
14479    assert_eq!(
14480        editor.update(cx, |editor, cx| editor.text(cx)),
14481        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14482        "Autoformatting (via test prettier) was not applied to the original buffer text",
14483    );
14484}
14485
14486#[gpui::test]
14487async fn test_addition_reverts(cx: &mut TestAppContext) {
14488    init_test(cx, |_| {});
14489    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14490    let base_text = indoc! {r#"
14491        struct Row;
14492        struct Row1;
14493        struct Row2;
14494
14495        struct Row4;
14496        struct Row5;
14497        struct Row6;
14498
14499        struct Row8;
14500        struct Row9;
14501        struct Row10;"#};
14502
14503    // When addition hunks are not adjacent to carets, no hunk revert is performed
14504    assert_hunk_revert(
14505        indoc! {r#"struct Row;
14506                   struct Row1;
14507                   struct Row1.1;
14508                   struct Row1.2;
14509                   struct Row2;ˇ
14510
14511                   struct Row4;
14512                   struct Row5;
14513                   struct Row6;
14514
14515                   struct Row8;
14516                   ˇstruct Row9;
14517                   struct Row9.1;
14518                   struct Row9.2;
14519                   struct Row9.3;
14520                   struct Row10;"#},
14521        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14522        indoc! {r#"struct Row;
14523                   struct Row1;
14524                   struct Row1.1;
14525                   struct Row1.2;
14526                   struct Row2;ˇ
14527
14528                   struct Row4;
14529                   struct Row5;
14530                   struct Row6;
14531
14532                   struct Row8;
14533                   ˇstruct Row9;
14534                   struct Row9.1;
14535                   struct Row9.2;
14536                   struct Row9.3;
14537                   struct Row10;"#},
14538        base_text,
14539        &mut cx,
14540    );
14541    // Same for selections
14542    assert_hunk_revert(
14543        indoc! {r#"struct Row;
14544                   struct Row1;
14545                   struct Row2;
14546                   struct Row2.1;
14547                   struct Row2.2;
14548                   «ˇ
14549                   struct Row4;
14550                   struct» Row5;
14551                   «struct Row6;
14552                   ˇ»
14553                   struct Row9.1;
14554                   struct Row9.2;
14555                   struct Row9.3;
14556                   struct Row8;
14557                   struct Row9;
14558                   struct Row10;"#},
14559        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14560        indoc! {r#"struct Row;
14561                   struct Row1;
14562                   struct Row2;
14563                   struct Row2.1;
14564                   struct Row2.2;
14565                   «ˇ
14566                   struct Row4;
14567                   struct» Row5;
14568                   «struct Row6;
14569                   ˇ»
14570                   struct Row9.1;
14571                   struct Row9.2;
14572                   struct Row9.3;
14573                   struct Row8;
14574                   struct Row9;
14575                   struct Row10;"#},
14576        base_text,
14577        &mut cx,
14578    );
14579
14580    // When carets and selections intersect the addition hunks, those are reverted.
14581    // Adjacent carets got merged.
14582    assert_hunk_revert(
14583        indoc! {r#"struct Row;
14584                   ˇ// something on the top
14585                   struct Row1;
14586                   struct Row2;
14587                   struct Roˇw3.1;
14588                   struct Row2.2;
14589                   struct Row2.3;ˇ
14590
14591                   struct Row4;
14592                   struct ˇRow5.1;
14593                   struct Row5.2;
14594                   struct «Rowˇ»5.3;
14595                   struct Row5;
14596                   struct Row6;
14597                   ˇ
14598                   struct Row9.1;
14599                   struct «Rowˇ»9.2;
14600                   struct «ˇRow»9.3;
14601                   struct Row8;
14602                   struct Row9;
14603                   «ˇ// something on bottom»
14604                   struct Row10;"#},
14605        vec![
14606            DiffHunkStatusKind::Added,
14607            DiffHunkStatusKind::Added,
14608            DiffHunkStatusKind::Added,
14609            DiffHunkStatusKind::Added,
14610            DiffHunkStatusKind::Added,
14611        ],
14612        indoc! {r#"struct Row;
14613                   ˇstruct Row1;
14614                   struct Row2;
14615                   ˇ
14616                   struct Row4;
14617                   ˇstruct Row5;
14618                   struct Row6;
14619                   ˇ
14620                   ˇstruct Row8;
14621                   struct Row9;
14622                   ˇstruct Row10;"#},
14623        base_text,
14624        &mut cx,
14625    );
14626}
14627
14628#[gpui::test]
14629async fn test_modification_reverts(cx: &mut TestAppContext) {
14630    init_test(cx, |_| {});
14631    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14632    let base_text = indoc! {r#"
14633        struct Row;
14634        struct Row1;
14635        struct Row2;
14636
14637        struct Row4;
14638        struct Row5;
14639        struct Row6;
14640
14641        struct Row8;
14642        struct Row9;
14643        struct Row10;"#};
14644
14645    // Modification hunks behave the same as the addition ones.
14646    assert_hunk_revert(
14647        indoc! {r#"struct Row;
14648                   struct Row1;
14649                   struct Row33;
14650                   ˇ
14651                   struct Row4;
14652                   struct Row5;
14653                   struct Row6;
14654                   ˇ
14655                   struct Row99;
14656                   struct Row9;
14657                   struct Row10;"#},
14658        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14659        indoc! {r#"struct Row;
14660                   struct Row1;
14661                   struct Row33;
14662                   ˇ
14663                   struct Row4;
14664                   struct Row5;
14665                   struct Row6;
14666                   ˇ
14667                   struct Row99;
14668                   struct Row9;
14669                   struct Row10;"#},
14670        base_text,
14671        &mut cx,
14672    );
14673    assert_hunk_revert(
14674        indoc! {r#"struct Row;
14675                   struct Row1;
14676                   struct Row33;
14677                   «ˇ
14678                   struct Row4;
14679                   struct» Row5;
14680                   «struct Row6;
14681                   ˇ»
14682                   struct Row99;
14683                   struct Row9;
14684                   struct Row10;"#},
14685        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14686        indoc! {r#"struct Row;
14687                   struct Row1;
14688                   struct Row33;
14689                   «ˇ
14690                   struct Row4;
14691                   struct» Row5;
14692                   «struct Row6;
14693                   ˇ»
14694                   struct Row99;
14695                   struct Row9;
14696                   struct Row10;"#},
14697        base_text,
14698        &mut cx,
14699    );
14700
14701    assert_hunk_revert(
14702        indoc! {r#"ˇstruct Row1.1;
14703                   struct Row1;
14704                   «ˇstr»uct Row22;
14705
14706                   struct ˇRow44;
14707                   struct Row5;
14708                   struct «Rˇ»ow66;ˇ
14709
14710                   «struˇ»ct Row88;
14711                   struct Row9;
14712                   struct Row1011;ˇ"#},
14713        vec![
14714            DiffHunkStatusKind::Modified,
14715            DiffHunkStatusKind::Modified,
14716            DiffHunkStatusKind::Modified,
14717            DiffHunkStatusKind::Modified,
14718            DiffHunkStatusKind::Modified,
14719            DiffHunkStatusKind::Modified,
14720        ],
14721        indoc! {r#"struct Row;
14722                   ˇstruct Row1;
14723                   struct Row2;
14724                   ˇ
14725                   struct Row4;
14726                   ˇstruct Row5;
14727                   struct Row6;
14728                   ˇ
14729                   struct Row8;
14730                   ˇstruct Row9;
14731                   struct Row10;ˇ"#},
14732        base_text,
14733        &mut cx,
14734    );
14735}
14736
14737#[gpui::test]
14738async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14739    init_test(cx, |_| {});
14740    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14741    let base_text = indoc! {r#"
14742        one
14743
14744        two
14745        three
14746        "#};
14747
14748    cx.set_head_text(base_text);
14749    cx.set_state("\nˇ\n");
14750    cx.executor().run_until_parked();
14751    cx.update_editor(|editor, _window, cx| {
14752        editor.expand_selected_diff_hunks(cx);
14753    });
14754    cx.executor().run_until_parked();
14755    cx.update_editor(|editor, window, cx| {
14756        editor.backspace(&Default::default(), window, cx);
14757    });
14758    cx.run_until_parked();
14759    cx.assert_state_with_diff(
14760        indoc! {r#"
14761
14762        - two
14763        - threeˇ
14764        +
14765        "#}
14766        .to_string(),
14767    );
14768}
14769
14770#[gpui::test]
14771async fn test_deletion_reverts(cx: &mut TestAppContext) {
14772    init_test(cx, |_| {});
14773    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14774    let base_text = indoc! {r#"struct Row;
14775struct Row1;
14776struct Row2;
14777
14778struct Row4;
14779struct Row5;
14780struct Row6;
14781
14782struct Row8;
14783struct Row9;
14784struct Row10;"#};
14785
14786    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14787    assert_hunk_revert(
14788        indoc! {r#"struct Row;
14789                   struct Row2;
14790
14791                   ˇstruct Row4;
14792                   struct Row5;
14793                   struct Row6;
14794                   ˇ
14795                   struct Row8;
14796                   struct Row10;"#},
14797        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14798        indoc! {r#"struct Row;
14799                   struct Row2;
14800
14801                   ˇstruct Row4;
14802                   struct Row5;
14803                   struct Row6;
14804                   ˇ
14805                   struct Row8;
14806                   struct Row10;"#},
14807        base_text,
14808        &mut cx,
14809    );
14810    assert_hunk_revert(
14811        indoc! {r#"struct Row;
14812                   struct Row2;
14813
14814                   «ˇstruct Row4;
14815                   struct» Row5;
14816                   «struct Row6;
14817                   ˇ»
14818                   struct Row8;
14819                   struct Row10;"#},
14820        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14821        indoc! {r#"struct Row;
14822                   struct Row2;
14823
14824                   «ˇstruct Row4;
14825                   struct» Row5;
14826                   «struct Row6;
14827                   ˇ»
14828                   struct Row8;
14829                   struct Row10;"#},
14830        base_text,
14831        &mut cx,
14832    );
14833
14834    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14835    assert_hunk_revert(
14836        indoc! {r#"struct Row;
14837                   ˇstruct Row2;
14838
14839                   struct Row4;
14840                   struct Row5;
14841                   struct Row6;
14842
14843                   struct Row8;ˇ
14844                   struct Row10;"#},
14845        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14846        indoc! {r#"struct Row;
14847                   struct Row1;
14848                   ˇstruct Row2;
14849
14850                   struct Row4;
14851                   struct Row5;
14852                   struct Row6;
14853
14854                   struct Row8;ˇ
14855                   struct Row9;
14856                   struct Row10;"#},
14857        base_text,
14858        &mut cx,
14859    );
14860    assert_hunk_revert(
14861        indoc! {r#"struct Row;
14862                   struct Row2«ˇ;
14863                   struct Row4;
14864                   struct» Row5;
14865                   «struct Row6;
14866
14867                   struct Row8;ˇ»
14868                   struct Row10;"#},
14869        vec![
14870            DiffHunkStatusKind::Deleted,
14871            DiffHunkStatusKind::Deleted,
14872            DiffHunkStatusKind::Deleted,
14873        ],
14874        indoc! {r#"struct Row;
14875                   struct Row1;
14876                   struct Row2«ˇ;
14877
14878                   struct Row4;
14879                   struct» Row5;
14880                   «struct Row6;
14881
14882                   struct Row8;ˇ»
14883                   struct Row9;
14884                   struct Row10;"#},
14885        base_text,
14886        &mut cx,
14887    );
14888}
14889
14890#[gpui::test]
14891async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14892    init_test(cx, |_| {});
14893
14894    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14895    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14896    let base_text_3 =
14897        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14898
14899    let text_1 = edit_first_char_of_every_line(base_text_1);
14900    let text_2 = edit_first_char_of_every_line(base_text_2);
14901    let text_3 = edit_first_char_of_every_line(base_text_3);
14902
14903    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14904    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14905    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14906
14907    let multibuffer = cx.new(|cx| {
14908        let mut multibuffer = MultiBuffer::new(ReadWrite);
14909        multibuffer.push_excerpts(
14910            buffer_1.clone(),
14911            [
14912                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14913                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14914                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14915            ],
14916            cx,
14917        );
14918        multibuffer.push_excerpts(
14919            buffer_2.clone(),
14920            [
14921                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14922                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14923                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14924            ],
14925            cx,
14926        );
14927        multibuffer.push_excerpts(
14928            buffer_3.clone(),
14929            [
14930                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14931                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14932                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14933            ],
14934            cx,
14935        );
14936        multibuffer
14937    });
14938
14939    let fs = FakeFs::new(cx.executor());
14940    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14941    let (editor, cx) = cx
14942        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14943    editor.update_in(cx, |editor, _window, cx| {
14944        for (buffer, diff_base) in [
14945            (buffer_1.clone(), base_text_1),
14946            (buffer_2.clone(), base_text_2),
14947            (buffer_3.clone(), base_text_3),
14948        ] {
14949            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14950            editor
14951                .buffer
14952                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14953        }
14954    });
14955    cx.executor().run_until_parked();
14956
14957    editor.update_in(cx, |editor, window, cx| {
14958        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}");
14959        editor.select_all(&SelectAll, window, cx);
14960        editor.git_restore(&Default::default(), window, cx);
14961    });
14962    cx.executor().run_until_parked();
14963
14964    // When all ranges are selected, all buffer hunks are reverted.
14965    editor.update(cx, |editor, cx| {
14966        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");
14967    });
14968    buffer_1.update(cx, |buffer, _| {
14969        assert_eq!(buffer.text(), base_text_1);
14970    });
14971    buffer_2.update(cx, |buffer, _| {
14972        assert_eq!(buffer.text(), base_text_2);
14973    });
14974    buffer_3.update(cx, |buffer, _| {
14975        assert_eq!(buffer.text(), base_text_3);
14976    });
14977
14978    editor.update_in(cx, |editor, window, cx| {
14979        editor.undo(&Default::default(), window, cx);
14980    });
14981
14982    editor.update_in(cx, |editor, window, cx| {
14983        editor.change_selections(None, window, cx, |s| {
14984            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
14985        });
14986        editor.git_restore(&Default::default(), window, cx);
14987    });
14988
14989    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
14990    // but not affect buffer_2 and its related excerpts.
14991    editor.update(cx, |editor, cx| {
14992        assert_eq!(
14993            editor.text(cx),
14994            "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}"
14995        );
14996    });
14997    buffer_1.update(cx, |buffer, _| {
14998        assert_eq!(buffer.text(), base_text_1);
14999    });
15000    buffer_2.update(cx, |buffer, _| {
15001        assert_eq!(
15002            buffer.text(),
15003            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15004        );
15005    });
15006    buffer_3.update(cx, |buffer, _| {
15007        assert_eq!(
15008            buffer.text(),
15009            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15010        );
15011    });
15012
15013    fn edit_first_char_of_every_line(text: &str) -> String {
15014        text.split('\n')
15015            .map(|line| format!("X{}", &line[1..]))
15016            .collect::<Vec<_>>()
15017            .join("\n")
15018    }
15019}
15020
15021#[gpui::test]
15022async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15023    init_test(cx, |_| {});
15024
15025    let cols = 4;
15026    let rows = 10;
15027    let sample_text_1 = sample_text(rows, cols, 'a');
15028    assert_eq!(
15029        sample_text_1,
15030        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15031    );
15032    let sample_text_2 = sample_text(rows, cols, 'l');
15033    assert_eq!(
15034        sample_text_2,
15035        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15036    );
15037    let sample_text_3 = sample_text(rows, cols, 'v');
15038    assert_eq!(
15039        sample_text_3,
15040        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15041    );
15042
15043    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15044    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15045    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15046
15047    let multi_buffer = cx.new(|cx| {
15048        let mut multibuffer = MultiBuffer::new(ReadWrite);
15049        multibuffer.push_excerpts(
15050            buffer_1.clone(),
15051            [
15052                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15053                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15054                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15055            ],
15056            cx,
15057        );
15058        multibuffer.push_excerpts(
15059            buffer_2.clone(),
15060            [
15061                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15062                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15063                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15064            ],
15065            cx,
15066        );
15067        multibuffer.push_excerpts(
15068            buffer_3.clone(),
15069            [
15070                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15071                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15072                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15073            ],
15074            cx,
15075        );
15076        multibuffer
15077    });
15078
15079    let fs = FakeFs::new(cx.executor());
15080    fs.insert_tree(
15081        "/a",
15082        json!({
15083            "main.rs": sample_text_1,
15084            "other.rs": sample_text_2,
15085            "lib.rs": sample_text_3,
15086        }),
15087    )
15088    .await;
15089    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15090    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15091    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15092    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15093        Editor::new(
15094            EditorMode::full(),
15095            multi_buffer,
15096            Some(project.clone()),
15097            window,
15098            cx,
15099        )
15100    });
15101    let multibuffer_item_id = workspace
15102        .update(cx, |workspace, window, cx| {
15103            assert!(
15104                workspace.active_item(cx).is_none(),
15105                "active item should be None before the first item is added"
15106            );
15107            workspace.add_item_to_active_pane(
15108                Box::new(multi_buffer_editor.clone()),
15109                None,
15110                true,
15111                window,
15112                cx,
15113            );
15114            let active_item = workspace
15115                .active_item(cx)
15116                .expect("should have an active item after adding the multi buffer");
15117            assert!(
15118                !active_item.is_singleton(cx),
15119                "A multi buffer was expected to active after adding"
15120            );
15121            active_item.item_id()
15122        })
15123        .unwrap();
15124    cx.executor().run_until_parked();
15125
15126    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15127        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15128            s.select_ranges(Some(1..2))
15129        });
15130        editor.open_excerpts(&OpenExcerpts, window, cx);
15131    });
15132    cx.executor().run_until_parked();
15133    let first_item_id = workspace
15134        .update(cx, |workspace, window, cx| {
15135            let active_item = workspace
15136                .active_item(cx)
15137                .expect("should have an active item after navigating into the 1st buffer");
15138            let first_item_id = active_item.item_id();
15139            assert_ne!(
15140                first_item_id, multibuffer_item_id,
15141                "Should navigate into the 1st buffer and activate it"
15142            );
15143            assert!(
15144                active_item.is_singleton(cx),
15145                "New active item should be a singleton buffer"
15146            );
15147            assert_eq!(
15148                active_item
15149                    .act_as::<Editor>(cx)
15150                    .expect("should have navigated into an editor for the 1st buffer")
15151                    .read(cx)
15152                    .text(cx),
15153                sample_text_1
15154            );
15155
15156            workspace
15157                .go_back(workspace.active_pane().downgrade(), window, cx)
15158                .detach_and_log_err(cx);
15159
15160            first_item_id
15161        })
15162        .unwrap();
15163    cx.executor().run_until_parked();
15164    workspace
15165        .update(cx, |workspace, _, cx| {
15166            let active_item = workspace
15167                .active_item(cx)
15168                .expect("should have an active item after navigating back");
15169            assert_eq!(
15170                active_item.item_id(),
15171                multibuffer_item_id,
15172                "Should navigate back to the multi buffer"
15173            );
15174            assert!(!active_item.is_singleton(cx));
15175        })
15176        .unwrap();
15177
15178    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15179        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15180            s.select_ranges(Some(39..40))
15181        });
15182        editor.open_excerpts(&OpenExcerpts, window, cx);
15183    });
15184    cx.executor().run_until_parked();
15185    let second_item_id = workspace
15186        .update(cx, |workspace, window, cx| {
15187            let active_item = workspace
15188                .active_item(cx)
15189                .expect("should have an active item after navigating into the 2nd buffer");
15190            let second_item_id = active_item.item_id();
15191            assert_ne!(
15192                second_item_id, multibuffer_item_id,
15193                "Should navigate away from the multibuffer"
15194            );
15195            assert_ne!(
15196                second_item_id, first_item_id,
15197                "Should navigate into the 2nd buffer and activate it"
15198            );
15199            assert!(
15200                active_item.is_singleton(cx),
15201                "New active item should be a singleton buffer"
15202            );
15203            assert_eq!(
15204                active_item
15205                    .act_as::<Editor>(cx)
15206                    .expect("should have navigated into an editor")
15207                    .read(cx)
15208                    .text(cx),
15209                sample_text_2
15210            );
15211
15212            workspace
15213                .go_back(workspace.active_pane().downgrade(), window, cx)
15214                .detach_and_log_err(cx);
15215
15216            second_item_id
15217        })
15218        .unwrap();
15219    cx.executor().run_until_parked();
15220    workspace
15221        .update(cx, |workspace, _, cx| {
15222            let active_item = workspace
15223                .active_item(cx)
15224                .expect("should have an active item after navigating back from the 2nd buffer");
15225            assert_eq!(
15226                active_item.item_id(),
15227                multibuffer_item_id,
15228                "Should navigate back from the 2nd buffer to the multi buffer"
15229            );
15230            assert!(!active_item.is_singleton(cx));
15231        })
15232        .unwrap();
15233
15234    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15235        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15236            s.select_ranges(Some(70..70))
15237        });
15238        editor.open_excerpts(&OpenExcerpts, window, cx);
15239    });
15240    cx.executor().run_until_parked();
15241    workspace
15242        .update(cx, |workspace, window, cx| {
15243            let active_item = workspace
15244                .active_item(cx)
15245                .expect("should have an active item after navigating into the 3rd buffer");
15246            let third_item_id = active_item.item_id();
15247            assert_ne!(
15248                third_item_id, multibuffer_item_id,
15249                "Should navigate into the 3rd buffer and activate it"
15250            );
15251            assert_ne!(third_item_id, first_item_id);
15252            assert_ne!(third_item_id, second_item_id);
15253            assert!(
15254                active_item.is_singleton(cx),
15255                "New active item should be a singleton buffer"
15256            );
15257            assert_eq!(
15258                active_item
15259                    .act_as::<Editor>(cx)
15260                    .expect("should have navigated into an editor")
15261                    .read(cx)
15262                    .text(cx),
15263                sample_text_3
15264            );
15265
15266            workspace
15267                .go_back(workspace.active_pane().downgrade(), window, cx)
15268                .detach_and_log_err(cx);
15269        })
15270        .unwrap();
15271    cx.executor().run_until_parked();
15272    workspace
15273        .update(cx, |workspace, _, cx| {
15274            let active_item = workspace
15275                .active_item(cx)
15276                .expect("should have an active item after navigating back from the 3rd buffer");
15277            assert_eq!(
15278                active_item.item_id(),
15279                multibuffer_item_id,
15280                "Should navigate back from the 3rd buffer to the multi buffer"
15281            );
15282            assert!(!active_item.is_singleton(cx));
15283        })
15284        .unwrap();
15285}
15286
15287#[gpui::test]
15288async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15289    init_test(cx, |_| {});
15290
15291    let mut cx = EditorTestContext::new(cx).await;
15292
15293    let diff_base = r#"
15294        use some::mod;
15295
15296        const A: u32 = 42;
15297
15298        fn main() {
15299            println!("hello");
15300
15301            println!("world");
15302        }
15303        "#
15304    .unindent();
15305
15306    cx.set_state(
15307        &r#"
15308        use some::modified;
15309
15310        ˇ
15311        fn main() {
15312            println!("hello there");
15313
15314            println!("around the");
15315            println!("world");
15316        }
15317        "#
15318        .unindent(),
15319    );
15320
15321    cx.set_head_text(&diff_base);
15322    executor.run_until_parked();
15323
15324    cx.update_editor(|editor, window, cx| {
15325        editor.go_to_next_hunk(&GoToHunk, window, cx);
15326        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15327    });
15328    executor.run_until_parked();
15329    cx.assert_state_with_diff(
15330        r#"
15331          use some::modified;
15332
15333
15334          fn main() {
15335        -     println!("hello");
15336        + ˇ    println!("hello there");
15337
15338              println!("around the");
15339              println!("world");
15340          }
15341        "#
15342        .unindent(),
15343    );
15344
15345    cx.update_editor(|editor, window, cx| {
15346        for _ in 0..2 {
15347            editor.go_to_next_hunk(&GoToHunk, window, cx);
15348            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15349        }
15350    });
15351    executor.run_until_parked();
15352    cx.assert_state_with_diff(
15353        r#"
15354        - use some::mod;
15355        + ˇuse some::modified;
15356
15357
15358          fn main() {
15359        -     println!("hello");
15360        +     println!("hello there");
15361
15362        +     println!("around the");
15363              println!("world");
15364          }
15365        "#
15366        .unindent(),
15367    );
15368
15369    cx.update_editor(|editor, window, cx| {
15370        editor.go_to_next_hunk(&GoToHunk, window, cx);
15371        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15372    });
15373    executor.run_until_parked();
15374    cx.assert_state_with_diff(
15375        r#"
15376        - use some::mod;
15377        + use some::modified;
15378
15379        - const A: u32 = 42;
15380          ˇ
15381          fn main() {
15382        -     println!("hello");
15383        +     println!("hello there");
15384
15385        +     println!("around the");
15386              println!("world");
15387          }
15388        "#
15389        .unindent(),
15390    );
15391
15392    cx.update_editor(|editor, window, cx| {
15393        editor.cancel(&Cancel, window, cx);
15394    });
15395
15396    cx.assert_state_with_diff(
15397        r#"
15398          use some::modified;
15399
15400          ˇ
15401          fn main() {
15402              println!("hello there");
15403
15404              println!("around the");
15405              println!("world");
15406          }
15407        "#
15408        .unindent(),
15409    );
15410}
15411
15412#[gpui::test]
15413async fn test_diff_base_change_with_expanded_diff_hunks(
15414    executor: BackgroundExecutor,
15415    cx: &mut TestAppContext,
15416) {
15417    init_test(cx, |_| {});
15418
15419    let mut cx = EditorTestContext::new(cx).await;
15420
15421    let diff_base = r#"
15422        use some::mod1;
15423        use some::mod2;
15424
15425        const A: u32 = 42;
15426        const B: u32 = 42;
15427        const C: u32 = 42;
15428
15429        fn main() {
15430            println!("hello");
15431
15432            println!("world");
15433        }
15434        "#
15435    .unindent();
15436
15437    cx.set_state(
15438        &r#"
15439        use some::mod2;
15440
15441        const A: u32 = 42;
15442        const C: u32 = 42;
15443
15444        fn main(ˇ) {
15445            //println!("hello");
15446
15447            println!("world");
15448            //
15449            //
15450        }
15451        "#
15452        .unindent(),
15453    );
15454
15455    cx.set_head_text(&diff_base);
15456    executor.run_until_parked();
15457
15458    cx.update_editor(|editor, window, cx| {
15459        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15460    });
15461    executor.run_until_parked();
15462    cx.assert_state_with_diff(
15463        r#"
15464        - use some::mod1;
15465          use some::mod2;
15466
15467          const A: u32 = 42;
15468        - const B: u32 = 42;
15469          const C: u32 = 42;
15470
15471          fn main(ˇ) {
15472        -     println!("hello");
15473        +     //println!("hello");
15474
15475              println!("world");
15476        +     //
15477        +     //
15478          }
15479        "#
15480        .unindent(),
15481    );
15482
15483    cx.set_head_text("new diff base!");
15484    executor.run_until_parked();
15485    cx.assert_state_with_diff(
15486        r#"
15487        - new diff base!
15488        + use some::mod2;
15489        +
15490        + const A: u32 = 42;
15491        + const C: u32 = 42;
15492        +
15493        + fn main(ˇ) {
15494        +     //println!("hello");
15495        +
15496        +     println!("world");
15497        +     //
15498        +     //
15499        + }
15500        "#
15501        .unindent(),
15502    );
15503}
15504
15505#[gpui::test]
15506async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15507    init_test(cx, |_| {});
15508
15509    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15510    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15511    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15512    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15513    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15514    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15515
15516    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15517    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15518    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15519
15520    let multi_buffer = cx.new(|cx| {
15521        let mut multibuffer = MultiBuffer::new(ReadWrite);
15522        multibuffer.push_excerpts(
15523            buffer_1.clone(),
15524            [
15525                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15526                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15527                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15528            ],
15529            cx,
15530        );
15531        multibuffer.push_excerpts(
15532            buffer_2.clone(),
15533            [
15534                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15535                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15536                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15537            ],
15538            cx,
15539        );
15540        multibuffer.push_excerpts(
15541            buffer_3.clone(),
15542            [
15543                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15544                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15545                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15546            ],
15547            cx,
15548        );
15549        multibuffer
15550    });
15551
15552    let editor =
15553        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15554    editor
15555        .update(cx, |editor, _window, cx| {
15556            for (buffer, diff_base) in [
15557                (buffer_1.clone(), file_1_old),
15558                (buffer_2.clone(), file_2_old),
15559                (buffer_3.clone(), file_3_old),
15560            ] {
15561                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15562                editor
15563                    .buffer
15564                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15565            }
15566        })
15567        .unwrap();
15568
15569    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15570    cx.run_until_parked();
15571
15572    cx.assert_editor_state(
15573        &"
15574            ˇaaa
15575            ccc
15576            ddd
15577
15578            ggg
15579            hhh
15580
15581
15582            lll
15583            mmm
15584            NNN
15585
15586            qqq
15587            rrr
15588
15589            uuu
15590            111
15591            222
15592            333
15593
15594            666
15595            777
15596
15597            000
15598            !!!"
15599        .unindent(),
15600    );
15601
15602    cx.update_editor(|editor, window, cx| {
15603        editor.select_all(&SelectAll, window, cx);
15604        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15605    });
15606    cx.executor().run_until_parked();
15607
15608    cx.assert_state_with_diff(
15609        "
15610            «aaa
15611          - bbb
15612            ccc
15613            ddd
15614
15615            ggg
15616            hhh
15617
15618
15619            lll
15620            mmm
15621          - nnn
15622          + NNN
15623
15624            qqq
15625            rrr
15626
15627            uuu
15628            111
15629            222
15630            333
15631
15632          + 666
15633            777
15634
15635            000
15636            !!!ˇ»"
15637            .unindent(),
15638    );
15639}
15640
15641#[gpui::test]
15642async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15643    init_test(cx, |_| {});
15644
15645    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15646    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15647
15648    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15649    let multi_buffer = cx.new(|cx| {
15650        let mut multibuffer = MultiBuffer::new(ReadWrite);
15651        multibuffer.push_excerpts(
15652            buffer.clone(),
15653            [
15654                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15655                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15656                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15657            ],
15658            cx,
15659        );
15660        multibuffer
15661    });
15662
15663    let editor =
15664        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15665    editor
15666        .update(cx, |editor, _window, cx| {
15667            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15668            editor
15669                .buffer
15670                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15671        })
15672        .unwrap();
15673
15674    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15675    cx.run_until_parked();
15676
15677    cx.update_editor(|editor, window, cx| {
15678        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15679    });
15680    cx.executor().run_until_parked();
15681
15682    // When the start of a hunk coincides with the start of its excerpt,
15683    // the hunk is expanded. When the start of a a hunk is earlier than
15684    // the start of its excerpt, the hunk is not expanded.
15685    cx.assert_state_with_diff(
15686        "
15687            ˇaaa
15688          - bbb
15689          + BBB
15690
15691          - ddd
15692          - eee
15693          + DDD
15694          + EEE
15695            fff
15696
15697            iii
15698        "
15699        .unindent(),
15700    );
15701}
15702
15703#[gpui::test]
15704async fn test_edits_around_expanded_insertion_hunks(
15705    executor: BackgroundExecutor,
15706    cx: &mut TestAppContext,
15707) {
15708    init_test(cx, |_| {});
15709
15710    let mut cx = EditorTestContext::new(cx).await;
15711
15712    let diff_base = r#"
15713        use some::mod1;
15714        use some::mod2;
15715
15716        const A: u32 = 42;
15717
15718        fn main() {
15719            println!("hello");
15720
15721            println!("world");
15722        }
15723        "#
15724    .unindent();
15725    executor.run_until_parked();
15726    cx.set_state(
15727        &r#"
15728        use some::mod1;
15729        use some::mod2;
15730
15731        const A: u32 = 42;
15732        const B: u32 = 42;
15733        const C: u32 = 42;
15734        ˇ
15735
15736        fn main() {
15737            println!("hello");
15738
15739            println!("world");
15740        }
15741        "#
15742        .unindent(),
15743    );
15744
15745    cx.set_head_text(&diff_base);
15746    executor.run_until_parked();
15747
15748    cx.update_editor(|editor, window, cx| {
15749        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15750    });
15751    executor.run_until_parked();
15752
15753    cx.assert_state_with_diff(
15754        r#"
15755        use some::mod1;
15756        use some::mod2;
15757
15758        const A: u32 = 42;
15759      + const B: u32 = 42;
15760      + const C: u32 = 42;
15761      + ˇ
15762
15763        fn main() {
15764            println!("hello");
15765
15766            println!("world");
15767        }
15768      "#
15769        .unindent(),
15770    );
15771
15772    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15773    executor.run_until_parked();
15774
15775    cx.assert_state_with_diff(
15776        r#"
15777        use some::mod1;
15778        use some::mod2;
15779
15780        const A: u32 = 42;
15781      + const B: u32 = 42;
15782      + const C: u32 = 42;
15783      + const D: u32 = 42;
15784      + ˇ
15785
15786        fn main() {
15787            println!("hello");
15788
15789            println!("world");
15790        }
15791      "#
15792        .unindent(),
15793    );
15794
15795    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15796    executor.run_until_parked();
15797
15798    cx.assert_state_with_diff(
15799        r#"
15800        use some::mod1;
15801        use some::mod2;
15802
15803        const A: u32 = 42;
15804      + const B: u32 = 42;
15805      + const C: u32 = 42;
15806      + const D: u32 = 42;
15807      + const E: u32 = 42;
15808      + ˇ
15809
15810        fn main() {
15811            println!("hello");
15812
15813            println!("world");
15814        }
15815      "#
15816        .unindent(),
15817    );
15818
15819    cx.update_editor(|editor, window, cx| {
15820        editor.delete_line(&DeleteLine, window, cx);
15821    });
15822    executor.run_until_parked();
15823
15824    cx.assert_state_with_diff(
15825        r#"
15826        use some::mod1;
15827        use some::mod2;
15828
15829        const A: u32 = 42;
15830      + const B: u32 = 42;
15831      + const C: u32 = 42;
15832      + const D: u32 = 42;
15833      + const E: u32 = 42;
15834        ˇ
15835        fn main() {
15836            println!("hello");
15837
15838            println!("world");
15839        }
15840      "#
15841        .unindent(),
15842    );
15843
15844    cx.update_editor(|editor, window, cx| {
15845        editor.move_up(&MoveUp, window, cx);
15846        editor.delete_line(&DeleteLine, window, cx);
15847        editor.move_up(&MoveUp, window, cx);
15848        editor.delete_line(&DeleteLine, window, cx);
15849        editor.move_up(&MoveUp, window, cx);
15850        editor.delete_line(&DeleteLine, window, cx);
15851    });
15852    executor.run_until_parked();
15853    cx.assert_state_with_diff(
15854        r#"
15855        use some::mod1;
15856        use some::mod2;
15857
15858        const A: u32 = 42;
15859      + const B: u32 = 42;
15860        ˇ
15861        fn main() {
15862            println!("hello");
15863
15864            println!("world");
15865        }
15866      "#
15867        .unindent(),
15868    );
15869
15870    cx.update_editor(|editor, window, cx| {
15871        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15872        editor.delete_line(&DeleteLine, window, cx);
15873    });
15874    executor.run_until_parked();
15875    cx.assert_state_with_diff(
15876        r#"
15877        ˇ
15878        fn main() {
15879            println!("hello");
15880
15881            println!("world");
15882        }
15883      "#
15884        .unindent(),
15885    );
15886}
15887
15888#[gpui::test]
15889async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15890    init_test(cx, |_| {});
15891
15892    let mut cx = EditorTestContext::new(cx).await;
15893    cx.set_head_text(indoc! { "
15894        one
15895        two
15896        three
15897        four
15898        five
15899        "
15900    });
15901    cx.set_state(indoc! { "
15902        one
15903        ˇthree
15904        five
15905    "});
15906    cx.run_until_parked();
15907    cx.update_editor(|editor, window, cx| {
15908        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15909    });
15910    cx.assert_state_with_diff(
15911        indoc! { "
15912        one
15913      - two
15914        ˇthree
15915      - four
15916        five
15917    "}
15918        .to_string(),
15919    );
15920    cx.update_editor(|editor, window, cx| {
15921        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15922    });
15923
15924    cx.assert_state_with_diff(
15925        indoc! { "
15926        one
15927        ˇthree
15928        five
15929    "}
15930        .to_string(),
15931    );
15932
15933    cx.set_state(indoc! { "
15934        one
15935        ˇTWO
15936        three
15937        four
15938        five
15939    "});
15940    cx.run_until_parked();
15941    cx.update_editor(|editor, window, cx| {
15942        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15943    });
15944
15945    cx.assert_state_with_diff(
15946        indoc! { "
15947            one
15948          - two
15949          + ˇTWO
15950            three
15951            four
15952            five
15953        "}
15954        .to_string(),
15955    );
15956    cx.update_editor(|editor, window, cx| {
15957        editor.move_up(&Default::default(), window, cx);
15958        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15959    });
15960    cx.assert_state_with_diff(
15961        indoc! { "
15962            one
15963            ˇTWO
15964            three
15965            four
15966            five
15967        "}
15968        .to_string(),
15969    );
15970}
15971
15972#[gpui::test]
15973async fn test_edits_around_expanded_deletion_hunks(
15974    executor: BackgroundExecutor,
15975    cx: &mut TestAppContext,
15976) {
15977    init_test(cx, |_| {});
15978
15979    let mut cx = EditorTestContext::new(cx).await;
15980
15981    let diff_base = r#"
15982        use some::mod1;
15983        use some::mod2;
15984
15985        const A: u32 = 42;
15986        const B: u32 = 42;
15987        const C: u32 = 42;
15988
15989
15990        fn main() {
15991            println!("hello");
15992
15993            println!("world");
15994        }
15995    "#
15996    .unindent();
15997    executor.run_until_parked();
15998    cx.set_state(
15999        &r#"
16000        use some::mod1;
16001        use some::mod2;
16002
16003        ˇconst B: u32 = 42;
16004        const C: u32 = 42;
16005
16006
16007        fn main() {
16008            println!("hello");
16009
16010            println!("world");
16011        }
16012        "#
16013        .unindent(),
16014    );
16015
16016    cx.set_head_text(&diff_base);
16017    executor.run_until_parked();
16018
16019    cx.update_editor(|editor, window, cx| {
16020        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16021    });
16022    executor.run_until_parked();
16023
16024    cx.assert_state_with_diff(
16025        r#"
16026        use some::mod1;
16027        use some::mod2;
16028
16029      - const A: u32 = 42;
16030        ˇconst B: u32 = 42;
16031        const C: u32 = 42;
16032
16033
16034        fn main() {
16035            println!("hello");
16036
16037            println!("world");
16038        }
16039      "#
16040        .unindent(),
16041    );
16042
16043    cx.update_editor(|editor, window, cx| {
16044        editor.delete_line(&DeleteLine, window, cx);
16045    });
16046    executor.run_until_parked();
16047    cx.assert_state_with_diff(
16048        r#"
16049        use some::mod1;
16050        use some::mod2;
16051
16052      - const A: u32 = 42;
16053      - const B: u32 = 42;
16054        ˇconst C: u32 = 42;
16055
16056
16057        fn main() {
16058            println!("hello");
16059
16060            println!("world");
16061        }
16062      "#
16063        .unindent(),
16064    );
16065
16066    cx.update_editor(|editor, window, cx| {
16067        editor.delete_line(&DeleteLine, window, cx);
16068    });
16069    executor.run_until_parked();
16070    cx.assert_state_with_diff(
16071        r#"
16072        use some::mod1;
16073        use some::mod2;
16074
16075      - const A: u32 = 42;
16076      - const B: u32 = 42;
16077      - const C: u32 = 42;
16078        ˇ
16079
16080        fn main() {
16081            println!("hello");
16082
16083            println!("world");
16084        }
16085      "#
16086        .unindent(),
16087    );
16088
16089    cx.update_editor(|editor, window, cx| {
16090        editor.handle_input("replacement", window, cx);
16091    });
16092    executor.run_until_parked();
16093    cx.assert_state_with_diff(
16094        r#"
16095        use some::mod1;
16096        use some::mod2;
16097
16098      - const A: u32 = 42;
16099      - const B: u32 = 42;
16100      - const C: u32 = 42;
16101      -
16102      + replacementˇ
16103
16104        fn main() {
16105            println!("hello");
16106
16107            println!("world");
16108        }
16109      "#
16110        .unindent(),
16111    );
16112}
16113
16114#[gpui::test]
16115async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16116    init_test(cx, |_| {});
16117
16118    let mut cx = EditorTestContext::new(cx).await;
16119
16120    let base_text = r#"
16121        one
16122        two
16123        three
16124        four
16125        five
16126    "#
16127    .unindent();
16128    executor.run_until_parked();
16129    cx.set_state(
16130        &r#"
16131        one
16132        two
16133        fˇour
16134        five
16135        "#
16136        .unindent(),
16137    );
16138
16139    cx.set_head_text(&base_text);
16140    executor.run_until_parked();
16141
16142    cx.update_editor(|editor, window, cx| {
16143        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16144    });
16145    executor.run_until_parked();
16146
16147    cx.assert_state_with_diff(
16148        r#"
16149          one
16150          two
16151        - three
16152          fˇour
16153          five
16154        "#
16155        .unindent(),
16156    );
16157
16158    cx.update_editor(|editor, window, cx| {
16159        editor.backspace(&Backspace, window, cx);
16160        editor.backspace(&Backspace, window, cx);
16161    });
16162    executor.run_until_parked();
16163    cx.assert_state_with_diff(
16164        r#"
16165          one
16166          two
16167        - threeˇ
16168        - four
16169        + our
16170          five
16171        "#
16172        .unindent(),
16173    );
16174}
16175
16176#[gpui::test]
16177async fn test_edit_after_expanded_modification_hunk(
16178    executor: BackgroundExecutor,
16179    cx: &mut TestAppContext,
16180) {
16181    init_test(cx, |_| {});
16182
16183    let mut cx = EditorTestContext::new(cx).await;
16184
16185    let diff_base = r#"
16186        use some::mod1;
16187        use some::mod2;
16188
16189        const A: u32 = 42;
16190        const B: u32 = 42;
16191        const C: u32 = 42;
16192        const D: u32 = 42;
16193
16194
16195        fn main() {
16196            println!("hello");
16197
16198            println!("world");
16199        }"#
16200    .unindent();
16201
16202    cx.set_state(
16203        &r#"
16204        use some::mod1;
16205        use some::mod2;
16206
16207        const A: u32 = 42;
16208        const B: u32 = 42;
16209        const C: u32 = 43ˇ
16210        const D: u32 = 42;
16211
16212
16213        fn main() {
16214            println!("hello");
16215
16216            println!("world");
16217        }"#
16218        .unindent(),
16219    );
16220
16221    cx.set_head_text(&diff_base);
16222    executor.run_until_parked();
16223    cx.update_editor(|editor, window, cx| {
16224        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16225    });
16226    executor.run_until_parked();
16227
16228    cx.assert_state_with_diff(
16229        r#"
16230        use some::mod1;
16231        use some::mod2;
16232
16233        const A: u32 = 42;
16234        const B: u32 = 42;
16235      - const C: u32 = 42;
16236      + const C: u32 = 43ˇ
16237        const D: u32 = 42;
16238
16239
16240        fn main() {
16241            println!("hello");
16242
16243            println!("world");
16244        }"#
16245        .unindent(),
16246    );
16247
16248    cx.update_editor(|editor, window, cx| {
16249        editor.handle_input("\nnew_line\n", window, cx);
16250    });
16251    executor.run_until_parked();
16252
16253    cx.assert_state_with_diff(
16254        r#"
16255        use some::mod1;
16256        use some::mod2;
16257
16258        const A: u32 = 42;
16259        const B: u32 = 42;
16260      - const C: u32 = 42;
16261      + const C: u32 = 43
16262      + new_line
16263      + ˇ
16264        const D: u32 = 42;
16265
16266
16267        fn main() {
16268            println!("hello");
16269
16270            println!("world");
16271        }"#
16272        .unindent(),
16273    );
16274}
16275
16276#[gpui::test]
16277async fn test_stage_and_unstage_added_file_hunk(
16278    executor: BackgroundExecutor,
16279    cx: &mut TestAppContext,
16280) {
16281    init_test(cx, |_| {});
16282
16283    let mut cx = EditorTestContext::new(cx).await;
16284    cx.update_editor(|editor, _, cx| {
16285        editor.set_expand_all_diff_hunks(cx);
16286    });
16287
16288    let working_copy = r#"
16289            ˇfn main() {
16290                println!("hello, world!");
16291            }
16292        "#
16293    .unindent();
16294
16295    cx.set_state(&working_copy);
16296    executor.run_until_parked();
16297
16298    cx.assert_state_with_diff(
16299        r#"
16300            + ˇfn main() {
16301            +     println!("hello, world!");
16302            + }
16303        "#
16304        .unindent(),
16305    );
16306    cx.assert_index_text(None);
16307
16308    cx.update_editor(|editor, window, cx| {
16309        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16310    });
16311    executor.run_until_parked();
16312    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16313    cx.assert_state_with_diff(
16314        r#"
16315            + ˇfn main() {
16316            +     println!("hello, world!");
16317            + }
16318        "#
16319        .unindent(),
16320    );
16321
16322    cx.update_editor(|editor, window, cx| {
16323        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16324    });
16325    executor.run_until_parked();
16326    cx.assert_index_text(None);
16327}
16328
16329async fn setup_indent_guides_editor(
16330    text: &str,
16331    cx: &mut TestAppContext,
16332) -> (BufferId, EditorTestContext) {
16333    init_test(cx, |_| {});
16334
16335    let mut cx = EditorTestContext::new(cx).await;
16336
16337    let buffer_id = cx.update_editor(|editor, window, cx| {
16338        editor.set_text(text, window, cx);
16339        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16340
16341        buffer_ids[0]
16342    });
16343
16344    (buffer_id, cx)
16345}
16346
16347fn assert_indent_guides(
16348    range: Range<u32>,
16349    expected: Vec<IndentGuide>,
16350    active_indices: Option<Vec<usize>>,
16351    cx: &mut EditorTestContext,
16352) {
16353    let indent_guides = cx.update_editor(|editor, window, cx| {
16354        let snapshot = editor.snapshot(window, cx).display_snapshot;
16355        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16356            editor,
16357            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16358            true,
16359            &snapshot,
16360            cx,
16361        );
16362
16363        indent_guides.sort_by(|a, b| {
16364            a.depth.cmp(&b.depth).then(
16365                a.start_row
16366                    .cmp(&b.start_row)
16367                    .then(a.end_row.cmp(&b.end_row)),
16368            )
16369        });
16370        indent_guides
16371    });
16372
16373    if let Some(expected) = active_indices {
16374        let active_indices = cx.update_editor(|editor, window, cx| {
16375            let snapshot = editor.snapshot(window, cx).display_snapshot;
16376            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16377        });
16378
16379        assert_eq!(
16380            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16381            expected,
16382            "Active indent guide indices do not match"
16383        );
16384    }
16385
16386    assert_eq!(indent_guides, expected, "Indent guides do not match");
16387}
16388
16389fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16390    IndentGuide {
16391        buffer_id,
16392        start_row: MultiBufferRow(start_row),
16393        end_row: MultiBufferRow(end_row),
16394        depth,
16395        tab_size: 4,
16396        settings: IndentGuideSettings {
16397            enabled: true,
16398            line_width: 1,
16399            active_line_width: 1,
16400            ..Default::default()
16401        },
16402    }
16403}
16404
16405#[gpui::test]
16406async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16407    let (buffer_id, mut cx) = setup_indent_guides_editor(
16408        &"
16409    fn main() {
16410        let a = 1;
16411    }"
16412        .unindent(),
16413        cx,
16414    )
16415    .await;
16416
16417    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16418}
16419
16420#[gpui::test]
16421async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16422    let (buffer_id, mut cx) = setup_indent_guides_editor(
16423        &"
16424    fn main() {
16425        let a = 1;
16426        let b = 2;
16427    }"
16428        .unindent(),
16429        cx,
16430    )
16431    .await;
16432
16433    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16434}
16435
16436#[gpui::test]
16437async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16438    let (buffer_id, mut cx) = setup_indent_guides_editor(
16439        &"
16440    fn main() {
16441        let a = 1;
16442        if a == 3 {
16443            let b = 2;
16444        } else {
16445            let c = 3;
16446        }
16447    }"
16448        .unindent(),
16449        cx,
16450    )
16451    .await;
16452
16453    assert_indent_guides(
16454        0..8,
16455        vec![
16456            indent_guide(buffer_id, 1, 6, 0),
16457            indent_guide(buffer_id, 3, 3, 1),
16458            indent_guide(buffer_id, 5, 5, 1),
16459        ],
16460        None,
16461        &mut cx,
16462    );
16463}
16464
16465#[gpui::test]
16466async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16467    let (buffer_id, mut cx) = setup_indent_guides_editor(
16468        &"
16469    fn main() {
16470        let a = 1;
16471            let b = 2;
16472        let c = 3;
16473    }"
16474        .unindent(),
16475        cx,
16476    )
16477    .await;
16478
16479    assert_indent_guides(
16480        0..5,
16481        vec![
16482            indent_guide(buffer_id, 1, 3, 0),
16483            indent_guide(buffer_id, 2, 2, 1),
16484        ],
16485        None,
16486        &mut cx,
16487    );
16488}
16489
16490#[gpui::test]
16491async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16492    let (buffer_id, mut cx) = setup_indent_guides_editor(
16493        &"
16494        fn main() {
16495            let a = 1;
16496
16497            let c = 3;
16498        }"
16499        .unindent(),
16500        cx,
16501    )
16502    .await;
16503
16504    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16505}
16506
16507#[gpui::test]
16508async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16509    let (buffer_id, mut cx) = setup_indent_guides_editor(
16510        &"
16511        fn main() {
16512            let a = 1;
16513
16514            let c = 3;
16515
16516            if a == 3 {
16517                let b = 2;
16518            } else {
16519                let c = 3;
16520            }
16521        }"
16522        .unindent(),
16523        cx,
16524    )
16525    .await;
16526
16527    assert_indent_guides(
16528        0..11,
16529        vec![
16530            indent_guide(buffer_id, 1, 9, 0),
16531            indent_guide(buffer_id, 6, 6, 1),
16532            indent_guide(buffer_id, 8, 8, 1),
16533        ],
16534        None,
16535        &mut cx,
16536    );
16537}
16538
16539#[gpui::test]
16540async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16541    let (buffer_id, mut cx) = setup_indent_guides_editor(
16542        &"
16543        fn main() {
16544            let a = 1;
16545
16546            let c = 3;
16547
16548            if a == 3 {
16549                let b = 2;
16550            } else {
16551                let c = 3;
16552            }
16553        }"
16554        .unindent(),
16555        cx,
16556    )
16557    .await;
16558
16559    assert_indent_guides(
16560        1..11,
16561        vec![
16562            indent_guide(buffer_id, 1, 9, 0),
16563            indent_guide(buffer_id, 6, 6, 1),
16564            indent_guide(buffer_id, 8, 8, 1),
16565        ],
16566        None,
16567        &mut cx,
16568    );
16569}
16570
16571#[gpui::test]
16572async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16573    let (buffer_id, mut cx) = setup_indent_guides_editor(
16574        &"
16575        fn main() {
16576            let a = 1;
16577
16578            let c = 3;
16579
16580            if a == 3 {
16581                let b = 2;
16582            } else {
16583                let c = 3;
16584            }
16585        }"
16586        .unindent(),
16587        cx,
16588    )
16589    .await;
16590
16591    assert_indent_guides(
16592        1..10,
16593        vec![
16594            indent_guide(buffer_id, 1, 9, 0),
16595            indent_guide(buffer_id, 6, 6, 1),
16596            indent_guide(buffer_id, 8, 8, 1),
16597        ],
16598        None,
16599        &mut cx,
16600    );
16601}
16602
16603#[gpui::test]
16604async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16605    let (buffer_id, mut cx) = setup_indent_guides_editor(
16606        &"
16607        block1
16608            block2
16609                block3
16610                    block4
16611            block2
16612        block1
16613        block1"
16614            .unindent(),
16615        cx,
16616    )
16617    .await;
16618
16619    assert_indent_guides(
16620        1..10,
16621        vec![
16622            indent_guide(buffer_id, 1, 4, 0),
16623            indent_guide(buffer_id, 2, 3, 1),
16624            indent_guide(buffer_id, 3, 3, 2),
16625        ],
16626        None,
16627        &mut cx,
16628    );
16629}
16630
16631#[gpui::test]
16632async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16633    let (buffer_id, mut cx) = setup_indent_guides_editor(
16634        &"
16635        block1
16636            block2
16637                block3
16638
16639        block1
16640        block1"
16641            .unindent(),
16642        cx,
16643    )
16644    .await;
16645
16646    assert_indent_guides(
16647        0..6,
16648        vec![
16649            indent_guide(buffer_id, 1, 2, 0),
16650            indent_guide(buffer_id, 2, 2, 1),
16651        ],
16652        None,
16653        &mut cx,
16654    );
16655}
16656
16657#[gpui::test]
16658async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16659    let (buffer_id, mut cx) = setup_indent_guides_editor(
16660        &"
16661        block1
16662
16663
16664
16665            block2
16666        "
16667        .unindent(),
16668        cx,
16669    )
16670    .await;
16671
16672    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16673}
16674
16675#[gpui::test]
16676async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16677    let (buffer_id, mut cx) = setup_indent_guides_editor(
16678        &"
16679        def a:
16680        \tb = 3
16681        \tif True:
16682        \t\tc = 4
16683        \t\td = 5
16684        \tprint(b)
16685        "
16686        .unindent(),
16687        cx,
16688    )
16689    .await;
16690
16691    assert_indent_guides(
16692        0..6,
16693        vec![
16694            indent_guide(buffer_id, 1, 5, 0),
16695            indent_guide(buffer_id, 3, 4, 1),
16696        ],
16697        None,
16698        &mut cx,
16699    );
16700}
16701
16702#[gpui::test]
16703async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16704    let (buffer_id, mut cx) = setup_indent_guides_editor(
16705        &"
16706    fn main() {
16707        let a = 1;
16708    }"
16709        .unindent(),
16710        cx,
16711    )
16712    .await;
16713
16714    cx.update_editor(|editor, window, cx| {
16715        editor.change_selections(None, window, cx, |s| {
16716            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16717        });
16718    });
16719
16720    assert_indent_guides(
16721        0..3,
16722        vec![indent_guide(buffer_id, 1, 1, 0)],
16723        Some(vec![0]),
16724        &mut cx,
16725    );
16726}
16727
16728#[gpui::test]
16729async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16730    let (buffer_id, mut cx) = setup_indent_guides_editor(
16731        &"
16732    fn main() {
16733        if 1 == 2 {
16734            let a = 1;
16735        }
16736    }"
16737        .unindent(),
16738        cx,
16739    )
16740    .await;
16741
16742    cx.update_editor(|editor, window, cx| {
16743        editor.change_selections(None, window, cx, |s| {
16744            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16745        });
16746    });
16747
16748    assert_indent_guides(
16749        0..4,
16750        vec![
16751            indent_guide(buffer_id, 1, 3, 0),
16752            indent_guide(buffer_id, 2, 2, 1),
16753        ],
16754        Some(vec![1]),
16755        &mut cx,
16756    );
16757
16758    cx.update_editor(|editor, window, cx| {
16759        editor.change_selections(None, window, cx, |s| {
16760            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16761        });
16762    });
16763
16764    assert_indent_guides(
16765        0..4,
16766        vec![
16767            indent_guide(buffer_id, 1, 3, 0),
16768            indent_guide(buffer_id, 2, 2, 1),
16769        ],
16770        Some(vec![1]),
16771        &mut cx,
16772    );
16773
16774    cx.update_editor(|editor, window, cx| {
16775        editor.change_selections(None, window, cx, |s| {
16776            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16777        });
16778    });
16779
16780    assert_indent_guides(
16781        0..4,
16782        vec![
16783            indent_guide(buffer_id, 1, 3, 0),
16784            indent_guide(buffer_id, 2, 2, 1),
16785        ],
16786        Some(vec![0]),
16787        &mut cx,
16788    );
16789}
16790
16791#[gpui::test]
16792async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16793    let (buffer_id, mut cx) = setup_indent_guides_editor(
16794        &"
16795    fn main() {
16796        let a = 1;
16797
16798        let b = 2;
16799    }"
16800        .unindent(),
16801        cx,
16802    )
16803    .await;
16804
16805    cx.update_editor(|editor, window, cx| {
16806        editor.change_selections(None, window, cx, |s| {
16807            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16808        });
16809    });
16810
16811    assert_indent_guides(
16812        0..5,
16813        vec![indent_guide(buffer_id, 1, 3, 0)],
16814        Some(vec![0]),
16815        &mut cx,
16816    );
16817}
16818
16819#[gpui::test]
16820async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16821    let (buffer_id, mut cx) = setup_indent_guides_editor(
16822        &"
16823    def m:
16824        a = 1
16825        pass"
16826            .unindent(),
16827        cx,
16828    )
16829    .await;
16830
16831    cx.update_editor(|editor, window, cx| {
16832        editor.change_selections(None, window, cx, |s| {
16833            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16834        });
16835    });
16836
16837    assert_indent_guides(
16838        0..3,
16839        vec![indent_guide(buffer_id, 1, 2, 0)],
16840        Some(vec![0]),
16841        &mut cx,
16842    );
16843}
16844
16845#[gpui::test]
16846async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16847    init_test(cx, |_| {});
16848    let mut cx = EditorTestContext::new(cx).await;
16849    let text = indoc! {
16850        "
16851        impl A {
16852            fn b() {
16853                0;
16854                3;
16855                5;
16856                6;
16857                7;
16858            }
16859        }
16860        "
16861    };
16862    let base_text = indoc! {
16863        "
16864        impl A {
16865            fn b() {
16866                0;
16867                1;
16868                2;
16869                3;
16870                4;
16871            }
16872            fn c() {
16873                5;
16874                6;
16875                7;
16876            }
16877        }
16878        "
16879    };
16880
16881    cx.update_editor(|editor, window, cx| {
16882        editor.set_text(text, window, cx);
16883
16884        editor.buffer().update(cx, |multibuffer, cx| {
16885            let buffer = multibuffer.as_singleton().unwrap();
16886            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16887
16888            multibuffer.set_all_diff_hunks_expanded(cx);
16889            multibuffer.add_diff(diff, cx);
16890
16891            buffer.read(cx).remote_id()
16892        })
16893    });
16894    cx.run_until_parked();
16895
16896    cx.assert_state_with_diff(
16897        indoc! { "
16898          impl A {
16899              fn b() {
16900                  0;
16901        -         1;
16902        -         2;
16903                  3;
16904        -         4;
16905        -     }
16906        -     fn c() {
16907                  5;
16908                  6;
16909                  7;
16910              }
16911          }
16912          ˇ"
16913        }
16914        .to_string(),
16915    );
16916
16917    let mut actual_guides = cx.update_editor(|editor, window, cx| {
16918        editor
16919            .snapshot(window, cx)
16920            .buffer_snapshot
16921            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16922            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16923            .collect::<Vec<_>>()
16924    });
16925    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16926    assert_eq!(
16927        actual_guides,
16928        vec![
16929            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16930            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16931            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16932        ]
16933    );
16934}
16935
16936#[gpui::test]
16937async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16938    init_test(cx, |_| {});
16939    let mut cx = EditorTestContext::new(cx).await;
16940
16941    let diff_base = r#"
16942        a
16943        b
16944        c
16945        "#
16946    .unindent();
16947
16948    cx.set_state(
16949        &r#"
16950        ˇA
16951        b
16952        C
16953        "#
16954        .unindent(),
16955    );
16956    cx.set_head_text(&diff_base);
16957    cx.update_editor(|editor, window, cx| {
16958        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16959    });
16960    executor.run_until_parked();
16961
16962    let both_hunks_expanded = r#"
16963        - a
16964        + ˇA
16965          b
16966        - c
16967        + C
16968        "#
16969    .unindent();
16970
16971    cx.assert_state_with_diff(both_hunks_expanded.clone());
16972
16973    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16974        let snapshot = editor.snapshot(window, cx);
16975        let hunks = editor
16976            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16977            .collect::<Vec<_>>();
16978        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
16979        let buffer_id = hunks[0].buffer_id;
16980        hunks
16981            .into_iter()
16982            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
16983            .collect::<Vec<_>>()
16984    });
16985    assert_eq!(hunk_ranges.len(), 2);
16986
16987    cx.update_editor(|editor, _, cx| {
16988        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
16989    });
16990    executor.run_until_parked();
16991
16992    let second_hunk_expanded = r#"
16993          ˇA
16994          b
16995        - c
16996        + C
16997        "#
16998    .unindent();
16999
17000    cx.assert_state_with_diff(second_hunk_expanded);
17001
17002    cx.update_editor(|editor, _, cx| {
17003        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17004    });
17005    executor.run_until_parked();
17006
17007    cx.assert_state_with_diff(both_hunks_expanded.clone());
17008
17009    cx.update_editor(|editor, _, cx| {
17010        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17011    });
17012    executor.run_until_parked();
17013
17014    let first_hunk_expanded = r#"
17015        - a
17016        + ˇA
17017          b
17018          C
17019        "#
17020    .unindent();
17021
17022    cx.assert_state_with_diff(first_hunk_expanded);
17023
17024    cx.update_editor(|editor, _, cx| {
17025        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17026    });
17027    executor.run_until_parked();
17028
17029    cx.assert_state_with_diff(both_hunks_expanded);
17030
17031    cx.set_state(
17032        &r#"
17033        ˇA
17034        b
17035        "#
17036        .unindent(),
17037    );
17038    cx.run_until_parked();
17039
17040    // TODO this cursor position seems bad
17041    cx.assert_state_with_diff(
17042        r#"
17043        - ˇa
17044        + A
17045          b
17046        "#
17047        .unindent(),
17048    );
17049
17050    cx.update_editor(|editor, window, cx| {
17051        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17052    });
17053
17054    cx.assert_state_with_diff(
17055        r#"
17056            - ˇa
17057            + A
17058              b
17059            - c
17060            "#
17061        .unindent(),
17062    );
17063
17064    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17065        let snapshot = editor.snapshot(window, cx);
17066        let hunks = editor
17067            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17068            .collect::<Vec<_>>();
17069        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17070        let buffer_id = hunks[0].buffer_id;
17071        hunks
17072            .into_iter()
17073            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17074            .collect::<Vec<_>>()
17075    });
17076    assert_eq!(hunk_ranges.len(), 2);
17077
17078    cx.update_editor(|editor, _, cx| {
17079        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17080    });
17081    executor.run_until_parked();
17082
17083    cx.assert_state_with_diff(
17084        r#"
17085        - ˇa
17086        + A
17087          b
17088        "#
17089        .unindent(),
17090    );
17091}
17092
17093#[gpui::test]
17094async fn test_toggle_deletion_hunk_at_start_of_file(
17095    executor: BackgroundExecutor,
17096    cx: &mut TestAppContext,
17097) {
17098    init_test(cx, |_| {});
17099    let mut cx = EditorTestContext::new(cx).await;
17100
17101    let diff_base = r#"
17102        a
17103        b
17104        c
17105        "#
17106    .unindent();
17107
17108    cx.set_state(
17109        &r#"
17110        ˇb
17111        c
17112        "#
17113        .unindent(),
17114    );
17115    cx.set_head_text(&diff_base);
17116    cx.update_editor(|editor, window, cx| {
17117        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17118    });
17119    executor.run_until_parked();
17120
17121    let hunk_expanded = r#"
17122        - a
17123          ˇb
17124          c
17125        "#
17126    .unindent();
17127
17128    cx.assert_state_with_diff(hunk_expanded.clone());
17129
17130    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17131        let snapshot = editor.snapshot(window, cx);
17132        let hunks = editor
17133            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17134            .collect::<Vec<_>>();
17135        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17136        let buffer_id = hunks[0].buffer_id;
17137        hunks
17138            .into_iter()
17139            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17140            .collect::<Vec<_>>()
17141    });
17142    assert_eq!(hunk_ranges.len(), 1);
17143
17144    cx.update_editor(|editor, _, cx| {
17145        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17146    });
17147    executor.run_until_parked();
17148
17149    let hunk_collapsed = r#"
17150          ˇb
17151          c
17152        "#
17153    .unindent();
17154
17155    cx.assert_state_with_diff(hunk_collapsed);
17156
17157    cx.update_editor(|editor, _, cx| {
17158        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17159    });
17160    executor.run_until_parked();
17161
17162    cx.assert_state_with_diff(hunk_expanded.clone());
17163}
17164
17165#[gpui::test]
17166async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17167    init_test(cx, |_| {});
17168
17169    let fs = FakeFs::new(cx.executor());
17170    fs.insert_tree(
17171        path!("/test"),
17172        json!({
17173            ".git": {},
17174            "file-1": "ONE\n",
17175            "file-2": "TWO\n",
17176            "file-3": "THREE\n",
17177        }),
17178    )
17179    .await;
17180
17181    fs.set_head_for_repo(
17182        path!("/test/.git").as_ref(),
17183        &[
17184            ("file-1".into(), "one\n".into()),
17185            ("file-2".into(), "two\n".into()),
17186            ("file-3".into(), "three\n".into()),
17187        ],
17188    );
17189
17190    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17191    let mut buffers = vec![];
17192    for i in 1..=3 {
17193        let buffer = project
17194            .update(cx, |project, cx| {
17195                let path = format!(path!("/test/file-{}"), i);
17196                project.open_local_buffer(path, cx)
17197            })
17198            .await
17199            .unwrap();
17200        buffers.push(buffer);
17201    }
17202
17203    let multibuffer = cx.new(|cx| {
17204        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17205        multibuffer.set_all_diff_hunks_expanded(cx);
17206        for buffer in &buffers {
17207            let snapshot = buffer.read(cx).snapshot();
17208            multibuffer.set_excerpts_for_path(
17209                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17210                buffer.clone(),
17211                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17212                DEFAULT_MULTIBUFFER_CONTEXT,
17213                cx,
17214            );
17215        }
17216        multibuffer
17217    });
17218
17219    let editor = cx.add_window(|window, cx| {
17220        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17221    });
17222    cx.run_until_parked();
17223
17224    let snapshot = editor
17225        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17226        .unwrap();
17227    let hunks = snapshot
17228        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17229        .map(|hunk| match hunk {
17230            DisplayDiffHunk::Unfolded {
17231                display_row_range, ..
17232            } => display_row_range,
17233            DisplayDiffHunk::Folded { .. } => unreachable!(),
17234        })
17235        .collect::<Vec<_>>();
17236    assert_eq!(
17237        hunks,
17238        [
17239            DisplayRow(2)..DisplayRow(4),
17240            DisplayRow(7)..DisplayRow(9),
17241            DisplayRow(12)..DisplayRow(14),
17242        ]
17243    );
17244}
17245
17246#[gpui::test]
17247async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17248    init_test(cx, |_| {});
17249
17250    let mut cx = EditorTestContext::new(cx).await;
17251    cx.set_head_text(indoc! { "
17252        one
17253        two
17254        three
17255        four
17256        five
17257        "
17258    });
17259    cx.set_index_text(indoc! { "
17260        one
17261        two
17262        three
17263        four
17264        five
17265        "
17266    });
17267    cx.set_state(indoc! {"
17268        one
17269        TWO
17270        ˇTHREE
17271        FOUR
17272        five
17273    "});
17274    cx.run_until_parked();
17275    cx.update_editor(|editor, window, cx| {
17276        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17277    });
17278    cx.run_until_parked();
17279    cx.assert_index_text(Some(indoc! {"
17280        one
17281        TWO
17282        THREE
17283        FOUR
17284        five
17285    "}));
17286    cx.set_state(indoc! { "
17287        one
17288        TWO
17289        ˇTHREE-HUNDRED
17290        FOUR
17291        five
17292    "});
17293    cx.run_until_parked();
17294    cx.update_editor(|editor, window, cx| {
17295        let snapshot = editor.snapshot(window, cx);
17296        let hunks = editor
17297            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17298            .collect::<Vec<_>>();
17299        assert_eq!(hunks.len(), 1);
17300        assert_eq!(
17301            hunks[0].status(),
17302            DiffHunkStatus {
17303                kind: DiffHunkStatusKind::Modified,
17304                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17305            }
17306        );
17307
17308        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17309    });
17310    cx.run_until_parked();
17311    cx.assert_index_text(Some(indoc! {"
17312        one
17313        TWO
17314        THREE-HUNDRED
17315        FOUR
17316        five
17317    "}));
17318}
17319
17320#[gpui::test]
17321fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17322    init_test(cx, |_| {});
17323
17324    let editor = cx.add_window(|window, cx| {
17325        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17326        build_editor(buffer, window, cx)
17327    });
17328
17329    let render_args = Arc::new(Mutex::new(None));
17330    let snapshot = editor
17331        .update(cx, |editor, window, cx| {
17332            let snapshot = editor.buffer().read(cx).snapshot(cx);
17333            let range =
17334                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17335
17336            struct RenderArgs {
17337                row: MultiBufferRow,
17338                folded: bool,
17339                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17340            }
17341
17342            let crease = Crease::inline(
17343                range,
17344                FoldPlaceholder::test(),
17345                {
17346                    let toggle_callback = render_args.clone();
17347                    move |row, folded, callback, _window, _cx| {
17348                        *toggle_callback.lock() = Some(RenderArgs {
17349                            row,
17350                            folded,
17351                            callback,
17352                        });
17353                        div()
17354                    }
17355                },
17356                |_row, _folded, _window, _cx| div(),
17357            );
17358
17359            editor.insert_creases(Some(crease), cx);
17360            let snapshot = editor.snapshot(window, cx);
17361            let _div = snapshot.render_crease_toggle(
17362                MultiBufferRow(1),
17363                false,
17364                cx.entity().clone(),
17365                window,
17366                cx,
17367            );
17368            snapshot
17369        })
17370        .unwrap();
17371
17372    let render_args = render_args.lock().take().unwrap();
17373    assert_eq!(render_args.row, MultiBufferRow(1));
17374    assert!(!render_args.folded);
17375    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17376
17377    cx.update_window(*editor, |_, window, cx| {
17378        (render_args.callback)(true, window, cx)
17379    })
17380    .unwrap();
17381    let snapshot = editor
17382        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17383        .unwrap();
17384    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17385
17386    cx.update_window(*editor, |_, window, cx| {
17387        (render_args.callback)(false, window, cx)
17388    })
17389    .unwrap();
17390    let snapshot = editor
17391        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17392        .unwrap();
17393    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17394}
17395
17396#[gpui::test]
17397async fn test_input_text(cx: &mut TestAppContext) {
17398    init_test(cx, |_| {});
17399    let mut cx = EditorTestContext::new(cx).await;
17400
17401    cx.set_state(
17402        &r#"ˇone
17403        two
17404
17405        three
17406        fourˇ
17407        five
17408
17409        siˇx"#
17410            .unindent(),
17411    );
17412
17413    cx.dispatch_action(HandleInput(String::new()));
17414    cx.assert_editor_state(
17415        &r#"ˇone
17416        two
17417
17418        three
17419        fourˇ
17420        five
17421
17422        siˇx"#
17423            .unindent(),
17424    );
17425
17426    cx.dispatch_action(HandleInput("AAAA".to_string()));
17427    cx.assert_editor_state(
17428        &r#"AAAAˇone
17429        two
17430
17431        three
17432        fourAAAAˇ
17433        five
17434
17435        siAAAAˇx"#
17436            .unindent(),
17437    );
17438}
17439
17440#[gpui::test]
17441async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17442    init_test(cx, |_| {});
17443
17444    let mut cx = EditorTestContext::new(cx).await;
17445    cx.set_state(
17446        r#"let foo = 1;
17447let foo = 2;
17448let foo = 3;
17449let fooˇ = 4;
17450let foo = 5;
17451let foo = 6;
17452let foo = 7;
17453let foo = 8;
17454let foo = 9;
17455let foo = 10;
17456let foo = 11;
17457let foo = 12;
17458let foo = 13;
17459let foo = 14;
17460let foo = 15;"#,
17461    );
17462
17463    cx.update_editor(|e, window, cx| {
17464        assert_eq!(
17465            e.next_scroll_position,
17466            NextScrollCursorCenterTopBottom::Center,
17467            "Default next scroll direction is center",
17468        );
17469
17470        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17471        assert_eq!(
17472            e.next_scroll_position,
17473            NextScrollCursorCenterTopBottom::Top,
17474            "After center, next scroll direction should be top",
17475        );
17476
17477        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17478        assert_eq!(
17479            e.next_scroll_position,
17480            NextScrollCursorCenterTopBottom::Bottom,
17481            "After top, next scroll direction should be bottom",
17482        );
17483
17484        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17485        assert_eq!(
17486            e.next_scroll_position,
17487            NextScrollCursorCenterTopBottom::Center,
17488            "After bottom, scrolling should start over",
17489        );
17490
17491        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17492        assert_eq!(
17493            e.next_scroll_position,
17494            NextScrollCursorCenterTopBottom::Top,
17495            "Scrolling continues if retriggered fast enough"
17496        );
17497    });
17498
17499    cx.executor()
17500        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17501    cx.executor().run_until_parked();
17502    cx.update_editor(|e, _, _| {
17503        assert_eq!(
17504            e.next_scroll_position,
17505            NextScrollCursorCenterTopBottom::Center,
17506            "If scrolling is not triggered fast enough, it should reset"
17507        );
17508    });
17509}
17510
17511#[gpui::test]
17512async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17513    init_test(cx, |_| {});
17514    let mut cx = EditorLspTestContext::new_rust(
17515        lsp::ServerCapabilities {
17516            definition_provider: Some(lsp::OneOf::Left(true)),
17517            references_provider: Some(lsp::OneOf::Left(true)),
17518            ..lsp::ServerCapabilities::default()
17519        },
17520        cx,
17521    )
17522    .await;
17523
17524    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17525        let go_to_definition = cx
17526            .lsp
17527            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17528                move |params, _| async move {
17529                    if empty_go_to_definition {
17530                        Ok(None)
17531                    } else {
17532                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17533                            uri: params.text_document_position_params.text_document.uri,
17534                            range: lsp::Range::new(
17535                                lsp::Position::new(4, 3),
17536                                lsp::Position::new(4, 6),
17537                            ),
17538                        })))
17539                    }
17540                },
17541            );
17542        let references = cx
17543            .lsp
17544            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17545                Ok(Some(vec![lsp::Location {
17546                    uri: params.text_document_position.text_document.uri,
17547                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17548                }]))
17549            });
17550        (go_to_definition, references)
17551    };
17552
17553    cx.set_state(
17554        &r#"fn one() {
17555            let mut a = ˇtwo();
17556        }
17557
17558        fn two() {}"#
17559            .unindent(),
17560    );
17561    set_up_lsp_handlers(false, &mut cx);
17562    let navigated = cx
17563        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17564        .await
17565        .expect("Failed to navigate to definition");
17566    assert_eq!(
17567        navigated,
17568        Navigated::Yes,
17569        "Should have navigated to definition from the GetDefinition response"
17570    );
17571    cx.assert_editor_state(
17572        &r#"fn one() {
17573            let mut a = two();
17574        }
17575
17576        fn «twoˇ»() {}"#
17577            .unindent(),
17578    );
17579
17580    let editors = cx.update_workspace(|workspace, _, cx| {
17581        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17582    });
17583    cx.update_editor(|_, _, test_editor_cx| {
17584        assert_eq!(
17585            editors.len(),
17586            1,
17587            "Initially, only one, test, editor should be open in the workspace"
17588        );
17589        assert_eq!(
17590            test_editor_cx.entity(),
17591            editors.last().expect("Asserted len is 1").clone()
17592        );
17593    });
17594
17595    set_up_lsp_handlers(true, &mut cx);
17596    let navigated = cx
17597        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17598        .await
17599        .expect("Failed to navigate to lookup references");
17600    assert_eq!(
17601        navigated,
17602        Navigated::Yes,
17603        "Should have navigated to references as a fallback after empty GoToDefinition response"
17604    );
17605    // We should not change the selections in the existing file,
17606    // if opening another milti buffer with the references
17607    cx.assert_editor_state(
17608        &r#"fn one() {
17609            let mut a = two();
17610        }
17611
17612        fn «twoˇ»() {}"#
17613            .unindent(),
17614    );
17615    let editors = cx.update_workspace(|workspace, _, cx| {
17616        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17617    });
17618    cx.update_editor(|_, _, test_editor_cx| {
17619        assert_eq!(
17620            editors.len(),
17621            2,
17622            "After falling back to references search, we open a new editor with the results"
17623        );
17624        let references_fallback_text = editors
17625            .into_iter()
17626            .find(|new_editor| *new_editor != test_editor_cx.entity())
17627            .expect("Should have one non-test editor now")
17628            .read(test_editor_cx)
17629            .text(test_editor_cx);
17630        assert_eq!(
17631            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17632            "Should use the range from the references response and not the GoToDefinition one"
17633        );
17634    });
17635}
17636
17637#[gpui::test]
17638async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17639    init_test(cx, |_| {});
17640    cx.update(|cx| {
17641        let mut editor_settings = EditorSettings::get_global(cx).clone();
17642        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17643        EditorSettings::override_global(editor_settings, cx);
17644    });
17645    let mut cx = EditorLspTestContext::new_rust(
17646        lsp::ServerCapabilities {
17647            definition_provider: Some(lsp::OneOf::Left(true)),
17648            references_provider: Some(lsp::OneOf::Left(true)),
17649            ..lsp::ServerCapabilities::default()
17650        },
17651        cx,
17652    )
17653    .await;
17654    let original_state = r#"fn one() {
17655        let mut a = ˇtwo();
17656    }
17657
17658    fn two() {}"#
17659        .unindent();
17660    cx.set_state(&original_state);
17661
17662    let mut go_to_definition = cx
17663        .lsp
17664        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17665            move |_, _| async move { Ok(None) },
17666        );
17667    let _references = cx
17668        .lsp
17669        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17670            panic!("Should not call for references with no go to definition fallback")
17671        });
17672
17673    let navigated = cx
17674        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17675        .await
17676        .expect("Failed to navigate to lookup references");
17677    go_to_definition
17678        .next()
17679        .await
17680        .expect("Should have called the go_to_definition handler");
17681
17682    assert_eq!(
17683        navigated,
17684        Navigated::No,
17685        "Should have navigated to references as a fallback after empty GoToDefinition response"
17686    );
17687    cx.assert_editor_state(&original_state);
17688    let editors = cx.update_workspace(|workspace, _, cx| {
17689        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17690    });
17691    cx.update_editor(|_, _, _| {
17692        assert_eq!(
17693            editors.len(),
17694            1,
17695            "After unsuccessful fallback, no other editor should have been opened"
17696        );
17697    });
17698}
17699
17700#[gpui::test]
17701async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17702    init_test(cx, |_| {});
17703
17704    let language = Arc::new(Language::new(
17705        LanguageConfig::default(),
17706        Some(tree_sitter_rust::LANGUAGE.into()),
17707    ));
17708
17709    let text = r#"
17710        #[cfg(test)]
17711        mod tests() {
17712            #[test]
17713            fn runnable_1() {
17714                let a = 1;
17715            }
17716
17717            #[test]
17718            fn runnable_2() {
17719                let a = 1;
17720                let b = 2;
17721            }
17722        }
17723    "#
17724    .unindent();
17725
17726    let fs = FakeFs::new(cx.executor());
17727    fs.insert_file("/file.rs", Default::default()).await;
17728
17729    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17730    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17731    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17732    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17733    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17734
17735    let editor = cx.new_window_entity(|window, cx| {
17736        Editor::new(
17737            EditorMode::full(),
17738            multi_buffer,
17739            Some(project.clone()),
17740            window,
17741            cx,
17742        )
17743    });
17744
17745    editor.update_in(cx, |editor, window, cx| {
17746        let snapshot = editor.buffer().read(cx).snapshot(cx);
17747        editor.tasks.insert(
17748            (buffer.read(cx).remote_id(), 3),
17749            RunnableTasks {
17750                templates: vec![],
17751                offset: snapshot.anchor_before(43),
17752                column: 0,
17753                extra_variables: HashMap::default(),
17754                context_range: BufferOffset(43)..BufferOffset(85),
17755            },
17756        );
17757        editor.tasks.insert(
17758            (buffer.read(cx).remote_id(), 8),
17759            RunnableTasks {
17760                templates: vec![],
17761                offset: snapshot.anchor_before(86),
17762                column: 0,
17763                extra_variables: HashMap::default(),
17764                context_range: BufferOffset(86)..BufferOffset(191),
17765            },
17766        );
17767
17768        // Test finding task when cursor is inside function body
17769        editor.change_selections(None, window, cx, |s| {
17770            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17771        });
17772        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17773        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17774
17775        // Test finding task when cursor is on function name
17776        editor.change_selections(None, window, cx, |s| {
17777            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17778        });
17779        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17780        assert_eq!(row, 8, "Should find task when cursor is on function name");
17781    });
17782}
17783
17784#[gpui::test]
17785async fn test_folding_buffers(cx: &mut TestAppContext) {
17786    init_test(cx, |_| {});
17787
17788    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17789    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17790    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17791
17792    let fs = FakeFs::new(cx.executor());
17793    fs.insert_tree(
17794        path!("/a"),
17795        json!({
17796            "first.rs": sample_text_1,
17797            "second.rs": sample_text_2,
17798            "third.rs": sample_text_3,
17799        }),
17800    )
17801    .await;
17802    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17803    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17804    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17805    let worktree = project.update(cx, |project, cx| {
17806        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17807        assert_eq!(worktrees.len(), 1);
17808        worktrees.pop().unwrap()
17809    });
17810    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17811
17812    let buffer_1 = project
17813        .update(cx, |project, cx| {
17814            project.open_buffer((worktree_id, "first.rs"), cx)
17815        })
17816        .await
17817        .unwrap();
17818    let buffer_2 = project
17819        .update(cx, |project, cx| {
17820            project.open_buffer((worktree_id, "second.rs"), cx)
17821        })
17822        .await
17823        .unwrap();
17824    let buffer_3 = project
17825        .update(cx, |project, cx| {
17826            project.open_buffer((worktree_id, "third.rs"), cx)
17827        })
17828        .await
17829        .unwrap();
17830
17831    let multi_buffer = cx.new(|cx| {
17832        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17833        multi_buffer.push_excerpts(
17834            buffer_1.clone(),
17835            [
17836                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17837                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17838                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17839            ],
17840            cx,
17841        );
17842        multi_buffer.push_excerpts(
17843            buffer_2.clone(),
17844            [
17845                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17846                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17847                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17848            ],
17849            cx,
17850        );
17851        multi_buffer.push_excerpts(
17852            buffer_3.clone(),
17853            [
17854                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17855                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17856                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17857            ],
17858            cx,
17859        );
17860        multi_buffer
17861    });
17862    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17863        Editor::new(
17864            EditorMode::full(),
17865            multi_buffer.clone(),
17866            Some(project.clone()),
17867            window,
17868            cx,
17869        )
17870    });
17871
17872    assert_eq!(
17873        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17874        "\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",
17875    );
17876
17877    multi_buffer_editor.update(cx, |editor, cx| {
17878        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17879    });
17880    assert_eq!(
17881        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17882        "\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",
17883        "After folding the first buffer, its text should not be displayed"
17884    );
17885
17886    multi_buffer_editor.update(cx, |editor, cx| {
17887        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17888    });
17889    assert_eq!(
17890        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17891        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17892        "After folding the second buffer, its text should not be displayed"
17893    );
17894
17895    multi_buffer_editor.update(cx, |editor, cx| {
17896        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17897    });
17898    assert_eq!(
17899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17900        "\n\n\n\n\n",
17901        "After folding the third buffer, its text should not be displayed"
17902    );
17903
17904    // Emulate selection inside the fold logic, that should work
17905    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17906        editor
17907            .snapshot(window, cx)
17908            .next_line_boundary(Point::new(0, 4));
17909    });
17910
17911    multi_buffer_editor.update(cx, |editor, cx| {
17912        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17913    });
17914    assert_eq!(
17915        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17916        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17917        "After unfolding the second buffer, its text should be displayed"
17918    );
17919
17920    // Typing inside of buffer 1 causes that buffer to be unfolded.
17921    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17922        assert_eq!(
17923            multi_buffer
17924                .read(cx)
17925                .snapshot(cx)
17926                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17927                .collect::<String>(),
17928            "bbbb"
17929        );
17930        editor.change_selections(None, window, cx, |selections| {
17931            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17932        });
17933        editor.handle_input("B", window, cx);
17934    });
17935
17936    assert_eq!(
17937        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17938        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17939        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17940    );
17941
17942    multi_buffer_editor.update(cx, |editor, cx| {
17943        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17944    });
17945    assert_eq!(
17946        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17947        "\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",
17948        "After unfolding the all buffers, all original text should be displayed"
17949    );
17950}
17951
17952#[gpui::test]
17953async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17954    init_test(cx, |_| {});
17955
17956    let sample_text_1 = "1111\n2222\n3333".to_string();
17957    let sample_text_2 = "4444\n5555\n6666".to_string();
17958    let sample_text_3 = "7777\n8888\n9999".to_string();
17959
17960    let fs = FakeFs::new(cx.executor());
17961    fs.insert_tree(
17962        path!("/a"),
17963        json!({
17964            "first.rs": sample_text_1,
17965            "second.rs": sample_text_2,
17966            "third.rs": sample_text_3,
17967        }),
17968    )
17969    .await;
17970    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17971    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17972    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17973    let worktree = project.update(cx, |project, cx| {
17974        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17975        assert_eq!(worktrees.len(), 1);
17976        worktrees.pop().unwrap()
17977    });
17978    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17979
17980    let buffer_1 = project
17981        .update(cx, |project, cx| {
17982            project.open_buffer((worktree_id, "first.rs"), cx)
17983        })
17984        .await
17985        .unwrap();
17986    let buffer_2 = project
17987        .update(cx, |project, cx| {
17988            project.open_buffer((worktree_id, "second.rs"), cx)
17989        })
17990        .await
17991        .unwrap();
17992    let buffer_3 = project
17993        .update(cx, |project, cx| {
17994            project.open_buffer((worktree_id, "third.rs"), cx)
17995        })
17996        .await
17997        .unwrap();
17998
17999    let multi_buffer = cx.new(|cx| {
18000        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18001        multi_buffer.push_excerpts(
18002            buffer_1.clone(),
18003            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18004            cx,
18005        );
18006        multi_buffer.push_excerpts(
18007            buffer_2.clone(),
18008            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18009            cx,
18010        );
18011        multi_buffer.push_excerpts(
18012            buffer_3.clone(),
18013            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18014            cx,
18015        );
18016        multi_buffer
18017    });
18018
18019    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18020        Editor::new(
18021            EditorMode::full(),
18022            multi_buffer,
18023            Some(project.clone()),
18024            window,
18025            cx,
18026        )
18027    });
18028
18029    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18030    assert_eq!(
18031        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18032        full_text,
18033    );
18034
18035    multi_buffer_editor.update(cx, |editor, cx| {
18036        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18037    });
18038    assert_eq!(
18039        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18040        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18041        "After folding the first buffer, its text should not be displayed"
18042    );
18043
18044    multi_buffer_editor.update(cx, |editor, cx| {
18045        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18046    });
18047
18048    assert_eq!(
18049        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18050        "\n\n\n\n\n\n7777\n8888\n9999",
18051        "After folding the second buffer, its text should not be displayed"
18052    );
18053
18054    multi_buffer_editor.update(cx, |editor, cx| {
18055        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18056    });
18057    assert_eq!(
18058        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18059        "\n\n\n\n\n",
18060        "After folding the third buffer, its text should not be displayed"
18061    );
18062
18063    multi_buffer_editor.update(cx, |editor, cx| {
18064        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18065    });
18066    assert_eq!(
18067        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18068        "\n\n\n\n4444\n5555\n6666\n\n",
18069        "After unfolding the second buffer, its text should be displayed"
18070    );
18071
18072    multi_buffer_editor.update(cx, |editor, cx| {
18073        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18074    });
18075    assert_eq!(
18076        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18077        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18078        "After unfolding the first buffer, its text should be displayed"
18079    );
18080
18081    multi_buffer_editor.update(cx, |editor, cx| {
18082        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18083    });
18084    assert_eq!(
18085        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18086        full_text,
18087        "After unfolding all buffers, all original text should be displayed"
18088    );
18089}
18090
18091#[gpui::test]
18092async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18093    init_test(cx, |_| {});
18094
18095    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18096
18097    let fs = FakeFs::new(cx.executor());
18098    fs.insert_tree(
18099        path!("/a"),
18100        json!({
18101            "main.rs": sample_text,
18102        }),
18103    )
18104    .await;
18105    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18106    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18107    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18108    let worktree = project.update(cx, |project, cx| {
18109        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18110        assert_eq!(worktrees.len(), 1);
18111        worktrees.pop().unwrap()
18112    });
18113    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18114
18115    let buffer_1 = project
18116        .update(cx, |project, cx| {
18117            project.open_buffer((worktree_id, "main.rs"), cx)
18118        })
18119        .await
18120        .unwrap();
18121
18122    let multi_buffer = cx.new(|cx| {
18123        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18124        multi_buffer.push_excerpts(
18125            buffer_1.clone(),
18126            [ExcerptRange::new(
18127                Point::new(0, 0)
18128                    ..Point::new(
18129                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18130                        0,
18131                    ),
18132            )],
18133            cx,
18134        );
18135        multi_buffer
18136    });
18137    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18138        Editor::new(
18139            EditorMode::full(),
18140            multi_buffer,
18141            Some(project.clone()),
18142            window,
18143            cx,
18144        )
18145    });
18146
18147    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18148    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18149        enum TestHighlight {}
18150        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18151        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18152        editor.highlight_text::<TestHighlight>(
18153            vec![highlight_range.clone()],
18154            HighlightStyle::color(Hsla::green()),
18155            cx,
18156        );
18157        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18158    });
18159
18160    let full_text = format!("\n\n{sample_text}");
18161    assert_eq!(
18162        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18163        full_text,
18164    );
18165}
18166
18167#[gpui::test]
18168async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18169    init_test(cx, |_| {});
18170    cx.update(|cx| {
18171        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18172            "keymaps/default-linux.json",
18173            cx,
18174        )
18175        .unwrap();
18176        cx.bind_keys(default_key_bindings);
18177    });
18178
18179    let (editor, cx) = cx.add_window_view(|window, cx| {
18180        let multi_buffer = MultiBuffer::build_multi(
18181            [
18182                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18183                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18184                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18185                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18186            ],
18187            cx,
18188        );
18189        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18190
18191        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18192        // fold all but the second buffer, so that we test navigating between two
18193        // adjacent folded buffers, as well as folded buffers at the start and
18194        // end the multibuffer
18195        editor.fold_buffer(buffer_ids[0], cx);
18196        editor.fold_buffer(buffer_ids[2], cx);
18197        editor.fold_buffer(buffer_ids[3], cx);
18198
18199        editor
18200    });
18201    cx.simulate_resize(size(px(1000.), px(1000.)));
18202
18203    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18204    cx.assert_excerpts_with_selections(indoc! {"
18205        [EXCERPT]
18206        ˇ[FOLDED]
18207        [EXCERPT]
18208        a1
18209        b1
18210        [EXCERPT]
18211        [FOLDED]
18212        [EXCERPT]
18213        [FOLDED]
18214        "
18215    });
18216    cx.simulate_keystroke("down");
18217    cx.assert_excerpts_with_selections(indoc! {"
18218        [EXCERPT]
18219        [FOLDED]
18220        [EXCERPT]
18221        ˇa1
18222        b1
18223        [EXCERPT]
18224        [FOLDED]
18225        [EXCERPT]
18226        [FOLDED]
18227        "
18228    });
18229    cx.simulate_keystroke("down");
18230    cx.assert_excerpts_with_selections(indoc! {"
18231        [EXCERPT]
18232        [FOLDED]
18233        [EXCERPT]
18234        a1
18235        ˇb1
18236        [EXCERPT]
18237        [FOLDED]
18238        [EXCERPT]
18239        [FOLDED]
18240        "
18241    });
18242    cx.simulate_keystroke("down");
18243    cx.assert_excerpts_with_selections(indoc! {"
18244        [EXCERPT]
18245        [FOLDED]
18246        [EXCERPT]
18247        a1
18248        b1
18249        ˇ[EXCERPT]
18250        [FOLDED]
18251        [EXCERPT]
18252        [FOLDED]
18253        "
18254    });
18255    cx.simulate_keystroke("down");
18256    cx.assert_excerpts_with_selections(indoc! {"
18257        [EXCERPT]
18258        [FOLDED]
18259        [EXCERPT]
18260        a1
18261        b1
18262        [EXCERPT]
18263        ˇ[FOLDED]
18264        [EXCERPT]
18265        [FOLDED]
18266        "
18267    });
18268    for _ in 0..5 {
18269        cx.simulate_keystroke("down");
18270        cx.assert_excerpts_with_selections(indoc! {"
18271            [EXCERPT]
18272            [FOLDED]
18273            [EXCERPT]
18274            a1
18275            b1
18276            [EXCERPT]
18277            [FOLDED]
18278            [EXCERPT]
18279            ˇ[FOLDED]
18280            "
18281        });
18282    }
18283
18284    cx.simulate_keystroke("up");
18285    cx.assert_excerpts_with_selections(indoc! {"
18286        [EXCERPT]
18287        [FOLDED]
18288        [EXCERPT]
18289        a1
18290        b1
18291        [EXCERPT]
18292        ˇ[FOLDED]
18293        [EXCERPT]
18294        [FOLDED]
18295        "
18296    });
18297    cx.simulate_keystroke("up");
18298    cx.assert_excerpts_with_selections(indoc! {"
18299        [EXCERPT]
18300        [FOLDED]
18301        [EXCERPT]
18302        a1
18303        b1
18304        ˇ[EXCERPT]
18305        [FOLDED]
18306        [EXCERPT]
18307        [FOLDED]
18308        "
18309    });
18310    cx.simulate_keystroke("up");
18311    cx.assert_excerpts_with_selections(indoc! {"
18312        [EXCERPT]
18313        [FOLDED]
18314        [EXCERPT]
18315        a1
18316        ˇb1
18317        [EXCERPT]
18318        [FOLDED]
18319        [EXCERPT]
18320        [FOLDED]
18321        "
18322    });
18323    cx.simulate_keystroke("up");
18324    cx.assert_excerpts_with_selections(indoc! {"
18325        [EXCERPT]
18326        [FOLDED]
18327        [EXCERPT]
18328        ˇa1
18329        b1
18330        [EXCERPT]
18331        [FOLDED]
18332        [EXCERPT]
18333        [FOLDED]
18334        "
18335    });
18336    for _ in 0..5 {
18337        cx.simulate_keystroke("up");
18338        cx.assert_excerpts_with_selections(indoc! {"
18339            [EXCERPT]
18340            ˇ[FOLDED]
18341            [EXCERPT]
18342            a1
18343            b1
18344            [EXCERPT]
18345            [FOLDED]
18346            [EXCERPT]
18347            [FOLDED]
18348            "
18349        });
18350    }
18351}
18352
18353#[gpui::test]
18354async fn test_inline_completion_text(cx: &mut TestAppContext) {
18355    init_test(cx, |_| {});
18356
18357    // Simple insertion
18358    assert_highlighted_edits(
18359        "Hello, world!",
18360        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18361        true,
18362        cx,
18363        |highlighted_edits, cx| {
18364            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18365            assert_eq!(highlighted_edits.highlights.len(), 1);
18366            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18367            assert_eq!(
18368                highlighted_edits.highlights[0].1.background_color,
18369                Some(cx.theme().status().created_background)
18370            );
18371        },
18372    )
18373    .await;
18374
18375    // Replacement
18376    assert_highlighted_edits(
18377        "This is a test.",
18378        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18379        false,
18380        cx,
18381        |highlighted_edits, cx| {
18382            assert_eq!(highlighted_edits.text, "That is a test.");
18383            assert_eq!(highlighted_edits.highlights.len(), 1);
18384            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18385            assert_eq!(
18386                highlighted_edits.highlights[0].1.background_color,
18387                Some(cx.theme().status().created_background)
18388            );
18389        },
18390    )
18391    .await;
18392
18393    // Multiple edits
18394    assert_highlighted_edits(
18395        "Hello, world!",
18396        vec![
18397            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18398            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18399        ],
18400        false,
18401        cx,
18402        |highlighted_edits, cx| {
18403            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18404            assert_eq!(highlighted_edits.highlights.len(), 2);
18405            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18406            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18407            assert_eq!(
18408                highlighted_edits.highlights[0].1.background_color,
18409                Some(cx.theme().status().created_background)
18410            );
18411            assert_eq!(
18412                highlighted_edits.highlights[1].1.background_color,
18413                Some(cx.theme().status().created_background)
18414            );
18415        },
18416    )
18417    .await;
18418
18419    // Multiple lines with edits
18420    assert_highlighted_edits(
18421        "First line\nSecond line\nThird line\nFourth line",
18422        vec![
18423            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18424            (
18425                Point::new(2, 0)..Point::new(2, 10),
18426                "New third line".to_string(),
18427            ),
18428            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18429        ],
18430        false,
18431        cx,
18432        |highlighted_edits, cx| {
18433            assert_eq!(
18434                highlighted_edits.text,
18435                "Second modified\nNew third line\nFourth updated line"
18436            );
18437            assert_eq!(highlighted_edits.highlights.len(), 3);
18438            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18439            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18440            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18441            for highlight in &highlighted_edits.highlights {
18442                assert_eq!(
18443                    highlight.1.background_color,
18444                    Some(cx.theme().status().created_background)
18445                );
18446            }
18447        },
18448    )
18449    .await;
18450}
18451
18452#[gpui::test]
18453async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18454    init_test(cx, |_| {});
18455
18456    // Deletion
18457    assert_highlighted_edits(
18458        "Hello, world!",
18459        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18460        true,
18461        cx,
18462        |highlighted_edits, cx| {
18463            assert_eq!(highlighted_edits.text, "Hello, world!");
18464            assert_eq!(highlighted_edits.highlights.len(), 1);
18465            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18466            assert_eq!(
18467                highlighted_edits.highlights[0].1.background_color,
18468                Some(cx.theme().status().deleted_background)
18469            );
18470        },
18471    )
18472    .await;
18473
18474    // Insertion
18475    assert_highlighted_edits(
18476        "Hello, world!",
18477        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18478        true,
18479        cx,
18480        |highlighted_edits, cx| {
18481            assert_eq!(highlighted_edits.highlights.len(), 1);
18482            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18483            assert_eq!(
18484                highlighted_edits.highlights[0].1.background_color,
18485                Some(cx.theme().status().created_background)
18486            );
18487        },
18488    )
18489    .await;
18490}
18491
18492async fn assert_highlighted_edits(
18493    text: &str,
18494    edits: Vec<(Range<Point>, String)>,
18495    include_deletions: bool,
18496    cx: &mut TestAppContext,
18497    assertion_fn: impl Fn(HighlightedText, &App),
18498) {
18499    let window = cx.add_window(|window, cx| {
18500        let buffer = MultiBuffer::build_simple(text, cx);
18501        Editor::new(EditorMode::full(), buffer, None, window, cx)
18502    });
18503    let cx = &mut VisualTestContext::from_window(*window, cx);
18504
18505    let (buffer, snapshot) = window
18506        .update(cx, |editor, _window, cx| {
18507            (
18508                editor.buffer().clone(),
18509                editor.buffer().read(cx).snapshot(cx),
18510            )
18511        })
18512        .unwrap();
18513
18514    let edits = edits
18515        .into_iter()
18516        .map(|(range, edit)| {
18517            (
18518                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18519                edit,
18520            )
18521        })
18522        .collect::<Vec<_>>();
18523
18524    let text_anchor_edits = edits
18525        .clone()
18526        .into_iter()
18527        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18528        .collect::<Vec<_>>();
18529
18530    let edit_preview = window
18531        .update(cx, |_, _window, cx| {
18532            buffer
18533                .read(cx)
18534                .as_singleton()
18535                .unwrap()
18536                .read(cx)
18537                .preview_edits(text_anchor_edits.into(), cx)
18538        })
18539        .unwrap()
18540        .await;
18541
18542    cx.update(|_window, cx| {
18543        let highlighted_edits = inline_completion_edit_text(
18544            &snapshot.as_singleton().unwrap().2,
18545            &edits,
18546            &edit_preview,
18547            include_deletions,
18548            cx,
18549        );
18550        assertion_fn(highlighted_edits, cx)
18551    });
18552}
18553
18554#[track_caller]
18555fn assert_breakpoint(
18556    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18557    path: &Arc<Path>,
18558    expected: Vec<(u32, Breakpoint)>,
18559) {
18560    if expected.len() == 0usize {
18561        assert!(!breakpoints.contains_key(path), "{}", path.display());
18562    } else {
18563        let mut breakpoint = breakpoints
18564            .get(path)
18565            .unwrap()
18566            .into_iter()
18567            .map(|breakpoint| {
18568                (
18569                    breakpoint.row,
18570                    Breakpoint {
18571                        message: breakpoint.message.clone(),
18572                        state: breakpoint.state,
18573                        condition: breakpoint.condition.clone(),
18574                        hit_condition: breakpoint.hit_condition.clone(),
18575                    },
18576                )
18577            })
18578            .collect::<Vec<_>>();
18579
18580        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18581
18582        assert_eq!(expected, breakpoint);
18583    }
18584}
18585
18586fn add_log_breakpoint_at_cursor(
18587    editor: &mut Editor,
18588    log_message: &str,
18589    window: &mut Window,
18590    cx: &mut Context<Editor>,
18591) {
18592    let (anchor, bp) = editor
18593        .breakpoints_at_cursors(window, cx)
18594        .first()
18595        .and_then(|(anchor, bp)| {
18596            if let Some(bp) = bp {
18597                Some((*anchor, bp.clone()))
18598            } else {
18599                None
18600            }
18601        })
18602        .unwrap_or_else(|| {
18603            let cursor_position: Point = editor.selections.newest(cx).head();
18604
18605            let breakpoint_position = editor
18606                .snapshot(window, cx)
18607                .display_snapshot
18608                .buffer_snapshot
18609                .anchor_before(Point::new(cursor_position.row, 0));
18610
18611            (breakpoint_position, Breakpoint::new_log(&log_message))
18612        });
18613
18614    editor.edit_breakpoint_at_anchor(
18615        anchor,
18616        bp,
18617        BreakpointEditAction::EditLogMessage(log_message.into()),
18618        cx,
18619    );
18620}
18621
18622#[gpui::test]
18623async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18624    init_test(cx, |_| {});
18625
18626    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18627    let fs = FakeFs::new(cx.executor());
18628    fs.insert_tree(
18629        path!("/a"),
18630        json!({
18631            "main.rs": sample_text,
18632        }),
18633    )
18634    .await;
18635    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18636    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18637    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18638
18639    let fs = FakeFs::new(cx.executor());
18640    fs.insert_tree(
18641        path!("/a"),
18642        json!({
18643            "main.rs": sample_text,
18644        }),
18645    )
18646    .await;
18647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18648    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18649    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18650    let worktree_id = workspace
18651        .update(cx, |workspace, _window, cx| {
18652            workspace.project().update(cx, |project, cx| {
18653                project.worktrees(cx).next().unwrap().read(cx).id()
18654            })
18655        })
18656        .unwrap();
18657
18658    let buffer = project
18659        .update(cx, |project, cx| {
18660            project.open_buffer((worktree_id, "main.rs"), cx)
18661        })
18662        .await
18663        .unwrap();
18664
18665    let (editor, cx) = cx.add_window_view(|window, cx| {
18666        Editor::new(
18667            EditorMode::full(),
18668            MultiBuffer::build_from_buffer(buffer, cx),
18669            Some(project.clone()),
18670            window,
18671            cx,
18672        )
18673    });
18674
18675    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18676    let abs_path = project.read_with(cx, |project, cx| {
18677        project
18678            .absolute_path(&project_path, cx)
18679            .map(|path_buf| Arc::from(path_buf.to_owned()))
18680            .unwrap()
18681    });
18682
18683    // assert we can add breakpoint on the first line
18684    editor.update_in(cx, |editor, window, cx| {
18685        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18686        editor.move_to_end(&MoveToEnd, window, cx);
18687        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18688    });
18689
18690    let breakpoints = editor.update(cx, |editor, cx| {
18691        editor
18692            .breakpoint_store()
18693            .as_ref()
18694            .unwrap()
18695            .read(cx)
18696            .all_breakpoints(cx)
18697            .clone()
18698    });
18699
18700    assert_eq!(1, breakpoints.len());
18701    assert_breakpoint(
18702        &breakpoints,
18703        &abs_path,
18704        vec![
18705            (0, Breakpoint::new_standard()),
18706            (3, Breakpoint::new_standard()),
18707        ],
18708    );
18709
18710    editor.update_in(cx, |editor, window, cx| {
18711        editor.move_to_beginning(&MoveToBeginning, window, cx);
18712        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18713    });
18714
18715    let breakpoints = editor.update(cx, |editor, cx| {
18716        editor
18717            .breakpoint_store()
18718            .as_ref()
18719            .unwrap()
18720            .read(cx)
18721            .all_breakpoints(cx)
18722            .clone()
18723    });
18724
18725    assert_eq!(1, breakpoints.len());
18726    assert_breakpoint(
18727        &breakpoints,
18728        &abs_path,
18729        vec![(3, Breakpoint::new_standard())],
18730    );
18731
18732    editor.update_in(cx, |editor, window, cx| {
18733        editor.move_to_end(&MoveToEnd, window, cx);
18734        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18735    });
18736
18737    let breakpoints = editor.update(cx, |editor, cx| {
18738        editor
18739            .breakpoint_store()
18740            .as_ref()
18741            .unwrap()
18742            .read(cx)
18743            .all_breakpoints(cx)
18744            .clone()
18745    });
18746
18747    assert_eq!(0, breakpoints.len());
18748    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18749}
18750
18751#[gpui::test]
18752async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18753    init_test(cx, |_| {});
18754
18755    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18756
18757    let fs = FakeFs::new(cx.executor());
18758    fs.insert_tree(
18759        path!("/a"),
18760        json!({
18761            "main.rs": sample_text,
18762        }),
18763    )
18764    .await;
18765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18766    let (workspace, cx) =
18767        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18768
18769    let worktree_id = workspace.update(cx, |workspace, cx| {
18770        workspace.project().update(cx, |project, cx| {
18771            project.worktrees(cx).next().unwrap().read(cx).id()
18772        })
18773    });
18774
18775    let buffer = project
18776        .update(cx, |project, cx| {
18777            project.open_buffer((worktree_id, "main.rs"), cx)
18778        })
18779        .await
18780        .unwrap();
18781
18782    let (editor, cx) = cx.add_window_view(|window, cx| {
18783        Editor::new(
18784            EditorMode::full(),
18785            MultiBuffer::build_from_buffer(buffer, cx),
18786            Some(project.clone()),
18787            window,
18788            cx,
18789        )
18790    });
18791
18792    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18793    let abs_path = project.read_with(cx, |project, cx| {
18794        project
18795            .absolute_path(&project_path, cx)
18796            .map(|path_buf| Arc::from(path_buf.to_owned()))
18797            .unwrap()
18798    });
18799
18800    editor.update_in(cx, |editor, window, cx| {
18801        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18802    });
18803
18804    let breakpoints = editor.update(cx, |editor, cx| {
18805        editor
18806            .breakpoint_store()
18807            .as_ref()
18808            .unwrap()
18809            .read(cx)
18810            .all_breakpoints(cx)
18811            .clone()
18812    });
18813
18814    assert_breakpoint(
18815        &breakpoints,
18816        &abs_path,
18817        vec![(0, Breakpoint::new_log("hello world"))],
18818    );
18819
18820    // Removing a log message from a log breakpoint should remove it
18821    editor.update_in(cx, |editor, window, cx| {
18822        add_log_breakpoint_at_cursor(editor, "", window, cx);
18823    });
18824
18825    let breakpoints = editor.update(cx, |editor, cx| {
18826        editor
18827            .breakpoint_store()
18828            .as_ref()
18829            .unwrap()
18830            .read(cx)
18831            .all_breakpoints(cx)
18832            .clone()
18833    });
18834
18835    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18836
18837    editor.update_in(cx, |editor, window, cx| {
18838        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18839        editor.move_to_end(&MoveToEnd, window, cx);
18840        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18841        // Not adding a log message to a standard breakpoint shouldn't remove it
18842        add_log_breakpoint_at_cursor(editor, "", window, cx);
18843    });
18844
18845    let breakpoints = editor.update(cx, |editor, cx| {
18846        editor
18847            .breakpoint_store()
18848            .as_ref()
18849            .unwrap()
18850            .read(cx)
18851            .all_breakpoints(cx)
18852            .clone()
18853    });
18854
18855    assert_breakpoint(
18856        &breakpoints,
18857        &abs_path,
18858        vec![
18859            (0, Breakpoint::new_standard()),
18860            (3, Breakpoint::new_standard()),
18861        ],
18862    );
18863
18864    editor.update_in(cx, |editor, window, cx| {
18865        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18866    });
18867
18868    let breakpoints = editor.update(cx, |editor, cx| {
18869        editor
18870            .breakpoint_store()
18871            .as_ref()
18872            .unwrap()
18873            .read(cx)
18874            .all_breakpoints(cx)
18875            .clone()
18876    });
18877
18878    assert_breakpoint(
18879        &breakpoints,
18880        &abs_path,
18881        vec![
18882            (0, Breakpoint::new_standard()),
18883            (3, Breakpoint::new_log("hello world")),
18884        ],
18885    );
18886
18887    editor.update_in(cx, |editor, window, cx| {
18888        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18889    });
18890
18891    let breakpoints = editor.update(cx, |editor, cx| {
18892        editor
18893            .breakpoint_store()
18894            .as_ref()
18895            .unwrap()
18896            .read(cx)
18897            .all_breakpoints(cx)
18898            .clone()
18899    });
18900
18901    assert_breakpoint(
18902        &breakpoints,
18903        &abs_path,
18904        vec![
18905            (0, Breakpoint::new_standard()),
18906            (3, Breakpoint::new_log("hello Earth!!")),
18907        ],
18908    );
18909}
18910
18911/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18912/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18913/// or when breakpoints were placed out of order. This tests for a regression too
18914#[gpui::test]
18915async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18916    init_test(cx, |_| {});
18917
18918    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18919    let fs = FakeFs::new(cx.executor());
18920    fs.insert_tree(
18921        path!("/a"),
18922        json!({
18923            "main.rs": sample_text,
18924        }),
18925    )
18926    .await;
18927    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18928    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18929    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18930
18931    let fs = FakeFs::new(cx.executor());
18932    fs.insert_tree(
18933        path!("/a"),
18934        json!({
18935            "main.rs": sample_text,
18936        }),
18937    )
18938    .await;
18939    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18940    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18941    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18942    let worktree_id = workspace
18943        .update(cx, |workspace, _window, cx| {
18944            workspace.project().update(cx, |project, cx| {
18945                project.worktrees(cx).next().unwrap().read(cx).id()
18946            })
18947        })
18948        .unwrap();
18949
18950    let buffer = project
18951        .update(cx, |project, cx| {
18952            project.open_buffer((worktree_id, "main.rs"), cx)
18953        })
18954        .await
18955        .unwrap();
18956
18957    let (editor, cx) = cx.add_window_view(|window, cx| {
18958        Editor::new(
18959            EditorMode::full(),
18960            MultiBuffer::build_from_buffer(buffer, cx),
18961            Some(project.clone()),
18962            window,
18963            cx,
18964        )
18965    });
18966
18967    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18968    let abs_path = project.read_with(cx, |project, cx| {
18969        project
18970            .absolute_path(&project_path, cx)
18971            .map(|path_buf| Arc::from(path_buf.to_owned()))
18972            .unwrap()
18973    });
18974
18975    // assert we can add breakpoint on the first line
18976    editor.update_in(cx, |editor, window, cx| {
18977        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18978        editor.move_to_end(&MoveToEnd, window, cx);
18979        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18980        editor.move_up(&MoveUp, window, cx);
18981        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18982    });
18983
18984    let breakpoints = editor.update(cx, |editor, cx| {
18985        editor
18986            .breakpoint_store()
18987            .as_ref()
18988            .unwrap()
18989            .read(cx)
18990            .all_breakpoints(cx)
18991            .clone()
18992    });
18993
18994    assert_eq!(1, breakpoints.len());
18995    assert_breakpoint(
18996        &breakpoints,
18997        &abs_path,
18998        vec![
18999            (0, Breakpoint::new_standard()),
19000            (2, Breakpoint::new_standard()),
19001            (3, Breakpoint::new_standard()),
19002        ],
19003    );
19004
19005    editor.update_in(cx, |editor, window, cx| {
19006        editor.move_to_beginning(&MoveToBeginning, window, cx);
19007        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19008        editor.move_to_end(&MoveToEnd, window, cx);
19009        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19010        // Disabling a breakpoint that doesn't exist should do nothing
19011        editor.move_up(&MoveUp, window, cx);
19012        editor.move_up(&MoveUp, window, cx);
19013        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19014    });
19015
19016    let breakpoints = editor.update(cx, |editor, cx| {
19017        editor
19018            .breakpoint_store()
19019            .as_ref()
19020            .unwrap()
19021            .read(cx)
19022            .all_breakpoints(cx)
19023            .clone()
19024    });
19025
19026    let disable_breakpoint = {
19027        let mut bp = Breakpoint::new_standard();
19028        bp.state = BreakpointState::Disabled;
19029        bp
19030    };
19031
19032    assert_eq!(1, breakpoints.len());
19033    assert_breakpoint(
19034        &breakpoints,
19035        &abs_path,
19036        vec![
19037            (0, disable_breakpoint.clone()),
19038            (2, Breakpoint::new_standard()),
19039            (3, disable_breakpoint.clone()),
19040        ],
19041    );
19042
19043    editor.update_in(cx, |editor, window, cx| {
19044        editor.move_to_beginning(&MoveToBeginning, window, cx);
19045        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19046        editor.move_to_end(&MoveToEnd, window, cx);
19047        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19048        editor.move_up(&MoveUp, window, cx);
19049        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19050    });
19051
19052    let breakpoints = editor.update(cx, |editor, cx| {
19053        editor
19054            .breakpoint_store()
19055            .as_ref()
19056            .unwrap()
19057            .read(cx)
19058            .all_breakpoints(cx)
19059            .clone()
19060    });
19061
19062    assert_eq!(1, breakpoints.len());
19063    assert_breakpoint(
19064        &breakpoints,
19065        &abs_path,
19066        vec![
19067            (0, Breakpoint::new_standard()),
19068            (2, disable_breakpoint),
19069            (3, Breakpoint::new_standard()),
19070        ],
19071    );
19072}
19073
19074#[gpui::test]
19075async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19076    init_test(cx, |_| {});
19077    let capabilities = lsp::ServerCapabilities {
19078        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19079            prepare_provider: Some(true),
19080            work_done_progress_options: Default::default(),
19081        })),
19082        ..Default::default()
19083    };
19084    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19085
19086    cx.set_state(indoc! {"
19087        struct Fˇoo {}
19088    "});
19089
19090    cx.update_editor(|editor, _, cx| {
19091        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19092        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19093        editor.highlight_background::<DocumentHighlightRead>(
19094            &[highlight_range],
19095            |c| c.editor_document_highlight_read_background,
19096            cx,
19097        );
19098    });
19099
19100    let mut prepare_rename_handler = cx
19101        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19102            move |_, _, _| async move {
19103                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19104                    start: lsp::Position {
19105                        line: 0,
19106                        character: 7,
19107                    },
19108                    end: lsp::Position {
19109                        line: 0,
19110                        character: 10,
19111                    },
19112                })))
19113            },
19114        );
19115    let prepare_rename_task = cx
19116        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19117        .expect("Prepare rename was not started");
19118    prepare_rename_handler.next().await.unwrap();
19119    prepare_rename_task.await.expect("Prepare rename failed");
19120
19121    let mut rename_handler =
19122        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19123            let edit = lsp::TextEdit {
19124                range: lsp::Range {
19125                    start: lsp::Position {
19126                        line: 0,
19127                        character: 7,
19128                    },
19129                    end: lsp::Position {
19130                        line: 0,
19131                        character: 10,
19132                    },
19133                },
19134                new_text: "FooRenamed".to_string(),
19135            };
19136            Ok(Some(lsp::WorkspaceEdit::new(
19137                // Specify the same edit twice
19138                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19139            )))
19140        });
19141    let rename_task = cx
19142        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19143        .expect("Confirm rename was not started");
19144    rename_handler.next().await.unwrap();
19145    rename_task.await.expect("Confirm rename failed");
19146    cx.run_until_parked();
19147
19148    // Despite two edits, only one is actually applied as those are identical
19149    cx.assert_editor_state(indoc! {"
19150        struct FooRenamedˇ {}
19151    "});
19152}
19153
19154#[gpui::test]
19155async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19156    init_test(cx, |_| {});
19157    // These capabilities indicate that the server does not support prepare rename.
19158    let capabilities = lsp::ServerCapabilities {
19159        rename_provider: Some(lsp::OneOf::Left(true)),
19160        ..Default::default()
19161    };
19162    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19163
19164    cx.set_state(indoc! {"
19165        struct Fˇoo {}
19166    "});
19167
19168    cx.update_editor(|editor, _window, cx| {
19169        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19170        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19171        editor.highlight_background::<DocumentHighlightRead>(
19172            &[highlight_range],
19173            |c| c.editor_document_highlight_read_background,
19174            cx,
19175        );
19176    });
19177
19178    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19179        .expect("Prepare rename was not started")
19180        .await
19181        .expect("Prepare rename failed");
19182
19183    let mut rename_handler =
19184        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19185            let edit = lsp::TextEdit {
19186                range: lsp::Range {
19187                    start: lsp::Position {
19188                        line: 0,
19189                        character: 7,
19190                    },
19191                    end: lsp::Position {
19192                        line: 0,
19193                        character: 10,
19194                    },
19195                },
19196                new_text: "FooRenamed".to_string(),
19197            };
19198            Ok(Some(lsp::WorkspaceEdit::new(
19199                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19200            )))
19201        });
19202    let rename_task = cx
19203        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19204        .expect("Confirm rename was not started");
19205    rename_handler.next().await.unwrap();
19206    rename_task.await.expect("Confirm rename failed");
19207    cx.run_until_parked();
19208
19209    // Correct range is renamed, as `surrounding_word` is used to find it.
19210    cx.assert_editor_state(indoc! {"
19211        struct FooRenamedˇ {}
19212    "});
19213}
19214
19215#[gpui::test]
19216async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19217    init_test(cx, |_| {});
19218    let mut cx = EditorTestContext::new(cx).await;
19219
19220    let language = Arc::new(
19221        Language::new(
19222            LanguageConfig::default(),
19223            Some(tree_sitter_html::LANGUAGE.into()),
19224        )
19225        .with_brackets_query(
19226            r#"
19227            ("<" @open "/>" @close)
19228            ("</" @open ">" @close)
19229            ("<" @open ">" @close)
19230            ("\"" @open "\"" @close)
19231            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19232        "#,
19233        )
19234        .unwrap(),
19235    );
19236    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19237
19238    cx.set_state(indoc! {"
19239        <span>ˇ</span>
19240    "});
19241    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19242    cx.assert_editor_state(indoc! {"
19243        <span>
19244        ˇ
19245        </span>
19246    "});
19247
19248    cx.set_state(indoc! {"
19249        <span><span></span>ˇ</span>
19250    "});
19251    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19252    cx.assert_editor_state(indoc! {"
19253        <span><span></span>
19254        ˇ</span>
19255    "});
19256
19257    cx.set_state(indoc! {"
19258        <span>ˇ
19259        </span>
19260    "});
19261    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19262    cx.assert_editor_state(indoc! {"
19263        <span>
19264        ˇ
19265        </span>
19266    "});
19267}
19268
19269#[gpui::test(iterations = 10)]
19270async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19271    init_test(cx, |_| {});
19272
19273    let fs = FakeFs::new(cx.executor());
19274    fs.insert_tree(
19275        path!("/dir"),
19276        json!({
19277            "a.ts": "a",
19278        }),
19279    )
19280    .await;
19281
19282    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19283    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19284    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19285
19286    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19287    language_registry.add(Arc::new(Language::new(
19288        LanguageConfig {
19289            name: "TypeScript".into(),
19290            matcher: LanguageMatcher {
19291                path_suffixes: vec!["ts".to_string()],
19292                ..Default::default()
19293            },
19294            ..Default::default()
19295        },
19296        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19297    )));
19298    let mut fake_language_servers = language_registry.register_fake_lsp(
19299        "TypeScript",
19300        FakeLspAdapter {
19301            capabilities: lsp::ServerCapabilities {
19302                code_lens_provider: Some(lsp::CodeLensOptions {
19303                    resolve_provider: Some(true),
19304                }),
19305                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19306                    commands: vec!["_the/command".to_string()],
19307                    ..lsp::ExecuteCommandOptions::default()
19308                }),
19309                ..lsp::ServerCapabilities::default()
19310            },
19311            ..FakeLspAdapter::default()
19312        },
19313    );
19314
19315    let (buffer, _handle) = project
19316        .update(cx, |p, cx| {
19317            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19318        })
19319        .await
19320        .unwrap();
19321    cx.executor().run_until_parked();
19322
19323    let fake_server = fake_language_servers.next().await.unwrap();
19324
19325    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19326    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19327    drop(buffer_snapshot);
19328    let actions = cx
19329        .update_window(*workspace, |_, window, cx| {
19330            project.code_actions(&buffer, anchor..anchor, window, cx)
19331        })
19332        .unwrap();
19333
19334    fake_server
19335        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19336            Ok(Some(vec![
19337                lsp::CodeLens {
19338                    range: lsp::Range::default(),
19339                    command: Some(lsp::Command {
19340                        title: "Code lens command".to_owned(),
19341                        command: "_the/command".to_owned(),
19342                        arguments: None,
19343                    }),
19344                    data: None,
19345                },
19346                lsp::CodeLens {
19347                    range: lsp::Range::default(),
19348                    command: Some(lsp::Command {
19349                        title: "Command not in capabilities".to_owned(),
19350                        command: "not in capabilities".to_owned(),
19351                        arguments: None,
19352                    }),
19353                    data: None,
19354                },
19355                lsp::CodeLens {
19356                    range: lsp::Range {
19357                        start: lsp::Position {
19358                            line: 1,
19359                            character: 1,
19360                        },
19361                        end: lsp::Position {
19362                            line: 1,
19363                            character: 1,
19364                        },
19365                    },
19366                    command: Some(lsp::Command {
19367                        title: "Command not in range".to_owned(),
19368                        command: "_the/command".to_owned(),
19369                        arguments: None,
19370                    }),
19371                    data: None,
19372                },
19373            ]))
19374        })
19375        .next()
19376        .await;
19377
19378    let actions = actions.await.unwrap();
19379    assert_eq!(
19380        actions.len(),
19381        1,
19382        "Should have only one valid action for the 0..0 range"
19383    );
19384    let action = actions[0].clone();
19385    let apply = project.update(cx, |project, cx| {
19386        project.apply_code_action(buffer.clone(), action, true, cx)
19387    });
19388
19389    // Resolving the code action does not populate its edits. In absence of
19390    // edits, we must execute the given command.
19391    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19392        |mut lens, _| async move {
19393            let lens_command = lens.command.as_mut().expect("should have a command");
19394            assert_eq!(lens_command.title, "Code lens command");
19395            lens_command.arguments = Some(vec![json!("the-argument")]);
19396            Ok(lens)
19397        },
19398    );
19399
19400    // While executing the command, the language server sends the editor
19401    // a `workspaceEdit` request.
19402    fake_server
19403        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19404            let fake = fake_server.clone();
19405            move |params, _| {
19406                assert_eq!(params.command, "_the/command");
19407                let fake = fake.clone();
19408                async move {
19409                    fake.server
19410                        .request::<lsp::request::ApplyWorkspaceEdit>(
19411                            lsp::ApplyWorkspaceEditParams {
19412                                label: None,
19413                                edit: lsp::WorkspaceEdit {
19414                                    changes: Some(
19415                                        [(
19416                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19417                                            vec![lsp::TextEdit {
19418                                                range: lsp::Range::new(
19419                                                    lsp::Position::new(0, 0),
19420                                                    lsp::Position::new(0, 0),
19421                                                ),
19422                                                new_text: "X".into(),
19423                                            }],
19424                                        )]
19425                                        .into_iter()
19426                                        .collect(),
19427                                    ),
19428                                    ..Default::default()
19429                                },
19430                            },
19431                        )
19432                        .await
19433                        .into_response()
19434                        .unwrap();
19435                    Ok(Some(json!(null)))
19436                }
19437            }
19438        })
19439        .next()
19440        .await;
19441
19442    // Applying the code lens command returns a project transaction containing the edits
19443    // sent by the language server in its `workspaceEdit` request.
19444    let transaction = apply.await.unwrap();
19445    assert!(transaction.0.contains_key(&buffer));
19446    buffer.update(cx, |buffer, cx| {
19447        assert_eq!(buffer.text(), "Xa");
19448        buffer.undo(cx);
19449        assert_eq!(buffer.text(), "a");
19450    });
19451}
19452
19453#[gpui::test]
19454async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19455    init_test(cx, |_| {});
19456
19457    let fs = FakeFs::new(cx.executor());
19458    let main_text = r#"fn main() {
19459println!("1");
19460println!("2");
19461println!("3");
19462println!("4");
19463println!("5");
19464}"#;
19465    let lib_text = "mod foo {}";
19466    fs.insert_tree(
19467        path!("/a"),
19468        json!({
19469            "lib.rs": lib_text,
19470            "main.rs": main_text,
19471        }),
19472    )
19473    .await;
19474
19475    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19476    let (workspace, cx) =
19477        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19478    let worktree_id = workspace.update(cx, |workspace, cx| {
19479        workspace.project().update(cx, |project, cx| {
19480            project.worktrees(cx).next().unwrap().read(cx).id()
19481        })
19482    });
19483
19484    let expected_ranges = vec![
19485        Point::new(0, 0)..Point::new(0, 0),
19486        Point::new(1, 0)..Point::new(1, 1),
19487        Point::new(2, 0)..Point::new(2, 2),
19488        Point::new(3, 0)..Point::new(3, 3),
19489    ];
19490
19491    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19492    let editor_1 = workspace
19493        .update_in(cx, |workspace, window, cx| {
19494            workspace.open_path(
19495                (worktree_id, "main.rs"),
19496                Some(pane_1.downgrade()),
19497                true,
19498                window,
19499                cx,
19500            )
19501        })
19502        .unwrap()
19503        .await
19504        .downcast::<Editor>()
19505        .unwrap();
19506    pane_1.update(cx, |pane, cx| {
19507        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19508        open_editor.update(cx, |editor, cx| {
19509            assert_eq!(
19510                editor.display_text(cx),
19511                main_text,
19512                "Original main.rs text on initial open",
19513            );
19514            assert_eq!(
19515                editor
19516                    .selections
19517                    .all::<Point>(cx)
19518                    .into_iter()
19519                    .map(|s| s.range())
19520                    .collect::<Vec<_>>(),
19521                vec![Point::zero()..Point::zero()],
19522                "Default selections on initial open",
19523            );
19524        })
19525    });
19526    editor_1.update_in(cx, |editor, window, cx| {
19527        editor.change_selections(None, window, cx, |s| {
19528            s.select_ranges(expected_ranges.clone());
19529        });
19530    });
19531
19532    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19533        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19534    });
19535    let editor_2 = workspace
19536        .update_in(cx, |workspace, window, cx| {
19537            workspace.open_path(
19538                (worktree_id, "main.rs"),
19539                Some(pane_2.downgrade()),
19540                true,
19541                window,
19542                cx,
19543            )
19544        })
19545        .unwrap()
19546        .await
19547        .downcast::<Editor>()
19548        .unwrap();
19549    pane_2.update(cx, |pane, cx| {
19550        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19551        open_editor.update(cx, |editor, cx| {
19552            assert_eq!(
19553                editor.display_text(cx),
19554                main_text,
19555                "Original main.rs text on initial open in another panel",
19556            );
19557            assert_eq!(
19558                editor
19559                    .selections
19560                    .all::<Point>(cx)
19561                    .into_iter()
19562                    .map(|s| s.range())
19563                    .collect::<Vec<_>>(),
19564                vec![Point::zero()..Point::zero()],
19565                "Default selections on initial open in another panel",
19566            );
19567        })
19568    });
19569
19570    editor_2.update_in(cx, |editor, window, cx| {
19571        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19572    });
19573
19574    let _other_editor_1 = workspace
19575        .update_in(cx, |workspace, window, cx| {
19576            workspace.open_path(
19577                (worktree_id, "lib.rs"),
19578                Some(pane_1.downgrade()),
19579                true,
19580                window,
19581                cx,
19582            )
19583        })
19584        .unwrap()
19585        .await
19586        .downcast::<Editor>()
19587        .unwrap();
19588    pane_1
19589        .update_in(cx, |pane, window, cx| {
19590            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19591                .unwrap()
19592        })
19593        .await
19594        .unwrap();
19595    drop(editor_1);
19596    pane_1.update(cx, |pane, cx| {
19597        pane.active_item()
19598            .unwrap()
19599            .downcast::<Editor>()
19600            .unwrap()
19601            .update(cx, |editor, cx| {
19602                assert_eq!(
19603                    editor.display_text(cx),
19604                    lib_text,
19605                    "Other file should be open and active",
19606                );
19607            });
19608        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19609    });
19610
19611    let _other_editor_2 = workspace
19612        .update_in(cx, |workspace, window, cx| {
19613            workspace.open_path(
19614                (worktree_id, "lib.rs"),
19615                Some(pane_2.downgrade()),
19616                true,
19617                window,
19618                cx,
19619            )
19620        })
19621        .unwrap()
19622        .await
19623        .downcast::<Editor>()
19624        .unwrap();
19625    pane_2
19626        .update_in(cx, |pane, window, cx| {
19627            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19628                .unwrap()
19629        })
19630        .await
19631        .unwrap();
19632    drop(editor_2);
19633    pane_2.update(cx, |pane, cx| {
19634        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19635        open_editor.update(cx, |editor, cx| {
19636            assert_eq!(
19637                editor.display_text(cx),
19638                lib_text,
19639                "Other file should be open and active in another panel too",
19640            );
19641        });
19642        assert_eq!(
19643            pane.items().count(),
19644            1,
19645            "No other editors should be open in another pane",
19646        );
19647    });
19648
19649    let _editor_1_reopened = workspace
19650        .update_in(cx, |workspace, window, cx| {
19651            workspace.open_path(
19652                (worktree_id, "main.rs"),
19653                Some(pane_1.downgrade()),
19654                true,
19655                window,
19656                cx,
19657            )
19658        })
19659        .unwrap()
19660        .await
19661        .downcast::<Editor>()
19662        .unwrap();
19663    let _editor_2_reopened = workspace
19664        .update_in(cx, |workspace, window, cx| {
19665            workspace.open_path(
19666                (worktree_id, "main.rs"),
19667                Some(pane_2.downgrade()),
19668                true,
19669                window,
19670                cx,
19671            )
19672        })
19673        .unwrap()
19674        .await
19675        .downcast::<Editor>()
19676        .unwrap();
19677    pane_1.update(cx, |pane, cx| {
19678        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19679        open_editor.update(cx, |editor, cx| {
19680            assert_eq!(
19681                editor.display_text(cx),
19682                main_text,
19683                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19684            );
19685            assert_eq!(
19686                editor
19687                    .selections
19688                    .all::<Point>(cx)
19689                    .into_iter()
19690                    .map(|s| s.range())
19691                    .collect::<Vec<_>>(),
19692                expected_ranges,
19693                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19694            );
19695        })
19696    });
19697    pane_2.update(cx, |pane, cx| {
19698        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19699        open_editor.update(cx, |editor, cx| {
19700            assert_eq!(
19701                editor.display_text(cx),
19702                r#"fn main() {
19703⋯rintln!("1");
19704⋯intln!("2");
19705⋯ntln!("3");
19706println!("4");
19707println!("5");
19708}"#,
19709                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19710            );
19711            assert_eq!(
19712                editor
19713                    .selections
19714                    .all::<Point>(cx)
19715                    .into_iter()
19716                    .map(|s| s.range())
19717                    .collect::<Vec<_>>(),
19718                vec![Point::zero()..Point::zero()],
19719                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19720            );
19721        })
19722    });
19723}
19724
19725#[gpui::test]
19726async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19727    init_test(cx, |_| {});
19728
19729    let fs = FakeFs::new(cx.executor());
19730    let main_text = r#"fn main() {
19731println!("1");
19732println!("2");
19733println!("3");
19734println!("4");
19735println!("5");
19736}"#;
19737    let lib_text = "mod foo {}";
19738    fs.insert_tree(
19739        path!("/a"),
19740        json!({
19741            "lib.rs": lib_text,
19742            "main.rs": main_text,
19743        }),
19744    )
19745    .await;
19746
19747    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19748    let (workspace, cx) =
19749        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19750    let worktree_id = workspace.update(cx, |workspace, cx| {
19751        workspace.project().update(cx, |project, cx| {
19752            project.worktrees(cx).next().unwrap().read(cx).id()
19753        })
19754    });
19755
19756    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19757    let editor = workspace
19758        .update_in(cx, |workspace, window, cx| {
19759            workspace.open_path(
19760                (worktree_id, "main.rs"),
19761                Some(pane.downgrade()),
19762                true,
19763                window,
19764                cx,
19765            )
19766        })
19767        .unwrap()
19768        .await
19769        .downcast::<Editor>()
19770        .unwrap();
19771    pane.update(cx, |pane, cx| {
19772        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19773        open_editor.update(cx, |editor, cx| {
19774            assert_eq!(
19775                editor.display_text(cx),
19776                main_text,
19777                "Original main.rs text on initial open",
19778            );
19779        })
19780    });
19781    editor.update_in(cx, |editor, window, cx| {
19782        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19783    });
19784
19785    cx.update_global(|store: &mut SettingsStore, cx| {
19786        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19787            s.restore_on_file_reopen = Some(false);
19788        });
19789    });
19790    editor.update_in(cx, |editor, window, cx| {
19791        editor.fold_ranges(
19792            vec![
19793                Point::new(1, 0)..Point::new(1, 1),
19794                Point::new(2, 0)..Point::new(2, 2),
19795                Point::new(3, 0)..Point::new(3, 3),
19796            ],
19797            false,
19798            window,
19799            cx,
19800        );
19801    });
19802    pane.update_in(cx, |pane, window, cx| {
19803        pane.close_all_items(&CloseAllItems::default(), window, cx)
19804            .unwrap()
19805    })
19806    .await
19807    .unwrap();
19808    pane.update(cx, |pane, _| {
19809        assert!(pane.active_item().is_none());
19810    });
19811    cx.update_global(|store: &mut SettingsStore, cx| {
19812        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19813            s.restore_on_file_reopen = Some(true);
19814        });
19815    });
19816
19817    let _editor_reopened = workspace
19818        .update_in(cx, |workspace, window, cx| {
19819            workspace.open_path(
19820                (worktree_id, "main.rs"),
19821                Some(pane.downgrade()),
19822                true,
19823                window,
19824                cx,
19825            )
19826        })
19827        .unwrap()
19828        .await
19829        .downcast::<Editor>()
19830        .unwrap();
19831    pane.update(cx, |pane, cx| {
19832        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19833        open_editor.update(cx, |editor, cx| {
19834            assert_eq!(
19835                editor.display_text(cx),
19836                main_text,
19837                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19838            );
19839        })
19840    });
19841}
19842
19843#[gpui::test]
19844async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
19845    struct EmptyModalView {
19846        focus_handle: gpui::FocusHandle,
19847    }
19848    impl EventEmitter<DismissEvent> for EmptyModalView {}
19849    impl Render for EmptyModalView {
19850        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
19851            div()
19852        }
19853    }
19854    impl Focusable for EmptyModalView {
19855        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
19856            self.focus_handle.clone()
19857        }
19858    }
19859    impl workspace::ModalView for EmptyModalView {}
19860    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
19861        EmptyModalView {
19862            focus_handle: cx.focus_handle(),
19863        }
19864    }
19865
19866    init_test(cx, |_| {});
19867
19868    let fs = FakeFs::new(cx.executor());
19869    let project = Project::test(fs, [], cx).await;
19870    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19871    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
19872    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19873    let editor = cx.new_window_entity(|window, cx| {
19874        Editor::new(
19875            EditorMode::full(),
19876            buffer,
19877            Some(project.clone()),
19878            window,
19879            cx,
19880        )
19881    });
19882    workspace
19883        .update(cx, |workspace, window, cx| {
19884            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
19885        })
19886        .unwrap();
19887    editor.update_in(cx, |editor, window, cx| {
19888        editor.open_context_menu(&OpenContextMenu, window, cx);
19889        assert!(editor.mouse_context_menu.is_some());
19890    });
19891    workspace
19892        .update(cx, |workspace, window, cx| {
19893            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
19894        })
19895        .unwrap();
19896    cx.read(|cx| {
19897        assert!(editor.read(cx).mouse_context_menu.is_none());
19898    });
19899}
19900
19901#[gpui::test]
19902async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
19903    init_test(cx, |_| {});
19904
19905    let fs = FakeFs::new(cx.executor());
19906    fs.insert_file(path!("/file.html"), Default::default())
19907        .await;
19908
19909    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19910
19911    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19912    let html_language = Arc::new(Language::new(
19913        LanguageConfig {
19914            name: "HTML".into(),
19915            matcher: LanguageMatcher {
19916                path_suffixes: vec!["html".to_string()],
19917                ..LanguageMatcher::default()
19918            },
19919            brackets: BracketPairConfig {
19920                pairs: vec![BracketPair {
19921                    start: "<".into(),
19922                    end: ">".into(),
19923                    close: true,
19924                    ..Default::default()
19925                }],
19926                ..Default::default()
19927            },
19928            ..Default::default()
19929        },
19930        Some(tree_sitter_html::LANGUAGE.into()),
19931    ));
19932    language_registry.add(html_language);
19933    let mut fake_servers = language_registry.register_fake_lsp(
19934        "HTML",
19935        FakeLspAdapter {
19936            capabilities: lsp::ServerCapabilities {
19937                completion_provider: Some(lsp::CompletionOptions {
19938                    resolve_provider: Some(true),
19939                    ..Default::default()
19940                }),
19941                ..Default::default()
19942            },
19943            ..Default::default()
19944        },
19945    );
19946
19947    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19948    let cx = &mut VisualTestContext::from_window(*workspace, cx);
19949
19950    let worktree_id = workspace
19951        .update(cx, |workspace, _window, cx| {
19952            workspace.project().update(cx, |project, cx| {
19953                project.worktrees(cx).next().unwrap().read(cx).id()
19954            })
19955        })
19956        .unwrap();
19957    project
19958        .update(cx, |project, cx| {
19959            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
19960        })
19961        .await
19962        .unwrap();
19963    let editor = workspace
19964        .update(cx, |workspace, window, cx| {
19965            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
19966        })
19967        .unwrap()
19968        .await
19969        .unwrap()
19970        .downcast::<Editor>()
19971        .unwrap();
19972
19973    let fake_server = fake_servers.next().await.unwrap();
19974    editor.update_in(cx, |editor, window, cx| {
19975        editor.set_text("<ad></ad>", window, cx);
19976        editor.change_selections(None, window, cx, |selections| {
19977            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
19978        });
19979        let Some((buffer, _)) = editor
19980            .buffer
19981            .read(cx)
19982            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
19983        else {
19984            panic!("Failed to get buffer for selection position");
19985        };
19986        let buffer = buffer.read(cx);
19987        let buffer_id = buffer.remote_id();
19988        let opening_range =
19989            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
19990        let closing_range =
19991            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
19992        let mut linked_ranges = HashMap::default();
19993        linked_ranges.insert(
19994            buffer_id,
19995            vec![(opening_range.clone(), vec![closing_range.clone()])],
19996        );
19997        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
19998    });
19999    let mut completion_handle =
20000        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20001            Ok(Some(lsp::CompletionResponse::Array(vec![
20002                lsp::CompletionItem {
20003                    label: "head".to_string(),
20004                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20005                        lsp::InsertReplaceEdit {
20006                            new_text: "head".to_string(),
20007                            insert: lsp::Range::new(
20008                                lsp::Position::new(0, 1),
20009                                lsp::Position::new(0, 3),
20010                            ),
20011                            replace: lsp::Range::new(
20012                                lsp::Position::new(0, 1),
20013                                lsp::Position::new(0, 3),
20014                            ),
20015                        },
20016                    )),
20017                    ..Default::default()
20018                },
20019            ])))
20020        });
20021    editor.update_in(cx, |editor, window, cx| {
20022        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20023    });
20024    cx.run_until_parked();
20025    completion_handle.next().await.unwrap();
20026    editor.update(cx, |editor, _| {
20027        assert!(
20028            editor.context_menu_visible(),
20029            "Completion menu should be visible"
20030        );
20031    });
20032    editor.update_in(cx, |editor, window, cx| {
20033        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20034    });
20035    cx.executor().run_until_parked();
20036    editor.update(cx, |editor, cx| {
20037        assert_eq!(editor.text(cx), "<head></head>");
20038    });
20039}
20040
20041#[gpui::test]
20042async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20043    init_test(cx, |_| {});
20044
20045    let fs = FakeFs::new(cx.executor());
20046    fs.insert_tree(
20047        path!("/root"),
20048        json!({
20049            "a": {
20050                "main.rs": "fn main() {}",
20051            },
20052            "foo": {
20053                "bar": {
20054                    "external_file.rs": "pub mod external {}",
20055                }
20056            }
20057        }),
20058    )
20059    .await;
20060
20061    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20062    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20063    language_registry.add(rust_lang());
20064    let _fake_servers = language_registry.register_fake_lsp(
20065        "Rust",
20066        FakeLspAdapter {
20067            ..FakeLspAdapter::default()
20068        },
20069    );
20070    let (workspace, cx) =
20071        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20072    let worktree_id = workspace.update(cx, |workspace, cx| {
20073        workspace.project().update(cx, |project, cx| {
20074            project.worktrees(cx).next().unwrap().read(cx).id()
20075        })
20076    });
20077
20078    let assert_language_servers_count =
20079        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20080            project.update(cx, |project, cx| {
20081                let current = project
20082                    .lsp_store()
20083                    .read(cx)
20084                    .as_local()
20085                    .unwrap()
20086                    .language_servers
20087                    .len();
20088                assert_eq!(expected, current, "{context}");
20089            });
20090        };
20091
20092    assert_language_servers_count(
20093        0,
20094        "No servers should be running before any file is open",
20095        cx,
20096    );
20097    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20098    let main_editor = workspace
20099        .update_in(cx, |workspace, window, cx| {
20100            workspace.open_path(
20101                (worktree_id, "main.rs"),
20102                Some(pane.downgrade()),
20103                true,
20104                window,
20105                cx,
20106            )
20107        })
20108        .unwrap()
20109        .await
20110        .downcast::<Editor>()
20111        .unwrap();
20112    pane.update(cx, |pane, cx| {
20113        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20114        open_editor.update(cx, |editor, cx| {
20115            assert_eq!(
20116                editor.display_text(cx),
20117                "fn main() {}",
20118                "Original main.rs text on initial open",
20119            );
20120        });
20121        assert_eq!(open_editor, main_editor);
20122    });
20123    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20124
20125    let external_editor = workspace
20126        .update_in(cx, |workspace, window, cx| {
20127            workspace.open_abs_path(
20128                PathBuf::from("/root/foo/bar/external_file.rs"),
20129                OpenOptions::default(),
20130                window,
20131                cx,
20132            )
20133        })
20134        .await
20135        .expect("opening external file")
20136        .downcast::<Editor>()
20137        .expect("downcasted external file's open element to editor");
20138    pane.update(cx, |pane, cx| {
20139        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20140        open_editor.update(cx, |editor, cx| {
20141            assert_eq!(
20142                editor.display_text(cx),
20143                "pub mod external {}",
20144                "External file is open now",
20145            );
20146        });
20147        assert_eq!(open_editor, external_editor);
20148    });
20149    assert_language_servers_count(
20150        1,
20151        "Second, external, *.rs file should join the existing server",
20152        cx,
20153    );
20154
20155    pane.update_in(cx, |pane, window, cx| {
20156        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20157    })
20158    .unwrap()
20159    .await
20160    .unwrap();
20161    pane.update_in(cx, |pane, window, cx| {
20162        pane.navigate_backward(window, cx);
20163    });
20164    cx.run_until_parked();
20165    pane.update(cx, |pane, cx| {
20166        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20167        open_editor.update(cx, |editor, cx| {
20168            assert_eq!(
20169                editor.display_text(cx),
20170                "pub mod external {}",
20171                "External file is open now",
20172            );
20173        });
20174    });
20175    assert_language_servers_count(
20176        1,
20177        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20178        cx,
20179    );
20180
20181    cx.update(|_, cx| {
20182        workspace::reload(&workspace::Reload::default(), cx);
20183    });
20184    assert_language_servers_count(
20185        1,
20186        "After reloading the worktree with local and external files opened, only one project should be started",
20187        cx,
20188    );
20189}
20190
20191fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
20192    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
20193    point..point
20194}
20195
20196fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
20197    let (text, ranges) = marked_text_ranges(marked_text, true);
20198    assert_eq!(editor.text(cx), text);
20199    assert_eq!(
20200        editor.selections.ranges(cx),
20201        ranges,
20202        "Assert selections are {}",
20203        marked_text
20204    );
20205}
20206
20207pub fn handle_signature_help_request(
20208    cx: &mut EditorLspTestContext,
20209    mocked_response: lsp::SignatureHelp,
20210) -> impl Future<Output = ()> + use<> {
20211    let mut request =
20212        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
20213            let mocked_response = mocked_response.clone();
20214            async move { Ok(Some(mocked_response)) }
20215        });
20216
20217    async move {
20218        request.next().await;
20219    }
20220}
20221
20222/// Handle completion request passing a marked string specifying where the completion
20223/// should be triggered from using '|' character, what range should be replaced, and what completions
20224/// should be returned using '<' and '>' to delimit the range.
20225///
20226/// Also see `handle_completion_request_with_insert_and_replace`.
20227#[track_caller]
20228pub fn handle_completion_request(
20229    cx: &mut EditorLspTestContext,
20230    marked_string: &str,
20231    completions: Vec<&'static str>,
20232    counter: Arc<AtomicUsize>,
20233) -> impl Future<Output = ()> {
20234    let complete_from_marker: TextRangeMarker = '|'.into();
20235    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20236    let (_, mut marked_ranges) = marked_text_ranges_by(
20237        marked_string,
20238        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20239    );
20240
20241    let complete_from_position =
20242        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20243    let replace_range =
20244        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20245
20246    let mut request =
20247        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20248            let completions = completions.clone();
20249            counter.fetch_add(1, atomic::Ordering::Release);
20250            async move {
20251                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20252                assert_eq!(
20253                    params.text_document_position.position,
20254                    complete_from_position
20255                );
20256                Ok(Some(lsp::CompletionResponse::Array(
20257                    completions
20258                        .iter()
20259                        .map(|completion_text| lsp::CompletionItem {
20260                            label: completion_text.to_string(),
20261                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20262                                range: replace_range,
20263                                new_text: completion_text.to_string(),
20264                            })),
20265                            ..Default::default()
20266                        })
20267                        .collect(),
20268                )))
20269            }
20270        });
20271
20272    async move {
20273        request.next().await;
20274    }
20275}
20276
20277/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
20278/// given instead, which also contains an `insert` range.
20279///
20280/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
20281/// that is, `replace_range.start..cursor_pos`.
20282pub fn handle_completion_request_with_insert_and_replace(
20283    cx: &mut EditorLspTestContext,
20284    marked_string: &str,
20285    completions: Vec<&'static str>,
20286    counter: Arc<AtomicUsize>,
20287) -> impl Future<Output = ()> {
20288    let complete_from_marker: TextRangeMarker = '|'.into();
20289    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20290    let (_, mut marked_ranges) = marked_text_ranges_by(
20291        marked_string,
20292        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20293    );
20294
20295    let complete_from_position =
20296        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20297    let replace_range =
20298        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20299
20300    let mut request =
20301        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20302            let completions = completions.clone();
20303            counter.fetch_add(1, atomic::Ordering::Release);
20304            async move {
20305                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20306                assert_eq!(
20307                    params.text_document_position.position, complete_from_position,
20308                    "marker `|` position doesn't match",
20309                );
20310                Ok(Some(lsp::CompletionResponse::Array(
20311                    completions
20312                        .iter()
20313                        .map(|completion_text| lsp::CompletionItem {
20314                            label: completion_text.to_string(),
20315                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20316                                lsp::InsertReplaceEdit {
20317                                    insert: lsp::Range {
20318                                        start: replace_range.start,
20319                                        end: complete_from_position,
20320                                    },
20321                                    replace: replace_range,
20322                                    new_text: completion_text.to_string(),
20323                                },
20324                            )),
20325                            ..Default::default()
20326                        })
20327                        .collect(),
20328                )))
20329            }
20330        });
20331
20332    async move {
20333        request.next().await;
20334    }
20335}
20336
20337fn handle_resolve_completion_request(
20338    cx: &mut EditorLspTestContext,
20339    edits: Option<Vec<(&'static str, &'static str)>>,
20340) -> impl Future<Output = ()> {
20341    let edits = edits.map(|edits| {
20342        edits
20343            .iter()
20344            .map(|(marked_string, new_text)| {
20345                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
20346                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
20347                lsp::TextEdit::new(replace_range, new_text.to_string())
20348            })
20349            .collect::<Vec<_>>()
20350    });
20351
20352    let mut request =
20353        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20354            let edits = edits.clone();
20355            async move {
20356                Ok(lsp::CompletionItem {
20357                    additional_text_edits: edits,
20358                    ..Default::default()
20359                })
20360            }
20361        });
20362
20363    async move {
20364        request.next().await;
20365    }
20366}
20367
20368pub(crate) fn update_test_language_settings(
20369    cx: &mut TestAppContext,
20370    f: impl Fn(&mut AllLanguageSettingsContent),
20371) {
20372    cx.update(|cx| {
20373        SettingsStore::update_global(cx, |store, cx| {
20374            store.update_user_settings::<AllLanguageSettings>(cx, f);
20375        });
20376    });
20377}
20378
20379pub(crate) fn update_test_project_settings(
20380    cx: &mut TestAppContext,
20381    f: impl Fn(&mut ProjectSettings),
20382) {
20383    cx.update(|cx| {
20384        SettingsStore::update_global(cx, |store, cx| {
20385            store.update_user_settings::<ProjectSettings>(cx, f);
20386        });
20387    });
20388}
20389
20390pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
20391    cx.update(|cx| {
20392        assets::Assets.load_test_fonts(cx);
20393        let store = SettingsStore::test(cx);
20394        cx.set_global(store);
20395        theme::init(theme::LoadThemes::JustBase, cx);
20396        release_channel::init(SemanticVersion::default(), cx);
20397        client::init_settings(cx);
20398        language::init(cx);
20399        Project::init_settings(cx);
20400        workspace::init_settings(cx);
20401        crate::init(cx);
20402    });
20403
20404    update_test_language_settings(cx, f);
20405}
20406
20407#[track_caller]
20408fn assert_hunk_revert(
20409    not_reverted_text_with_selections: &str,
20410    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
20411    expected_reverted_text_with_selections: &str,
20412    base_text: &str,
20413    cx: &mut EditorLspTestContext,
20414) {
20415    cx.set_state(not_reverted_text_with_selections);
20416    cx.set_head_text(base_text);
20417    cx.executor().run_until_parked();
20418
20419    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
20420        let snapshot = editor.snapshot(window, cx);
20421        let reverted_hunk_statuses = snapshot
20422            .buffer_snapshot
20423            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
20424            .map(|hunk| hunk.status().kind)
20425            .collect::<Vec<_>>();
20426
20427        editor.git_restore(&Default::default(), window, cx);
20428        reverted_hunk_statuses
20429    });
20430    cx.executor().run_until_parked();
20431    cx.assert_editor_state(expected_reverted_text_with_selections);
20432    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
20433}