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 we add comment prefix when existing line contains space
 2776        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2777        cx.assert_editor_state(
 2778            indoc! {"
 2779        // Foo
 2780        //s
 2781        // ˇ
 2782    "}
 2783            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2784            .as_str(),
 2785        );
 2786        // Ensure that we add comment prefix when existing line does not contain space
 2787        cx.set_state(indoc! {"
 2788        // Foo
 2789        //ˇ
 2790    "});
 2791        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2792        cx.assert_editor_state(indoc! {"
 2793        // Foo
 2794        //
 2795        // ˇ
 2796    "});
 2797        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2798        cx.set_state(indoc! {"
 2799        ˇ// Foo
 2800    "});
 2801        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2802        cx.assert_editor_state(indoc! {"
 2803
 2804        ˇ// Foo
 2805    "});
 2806    }
 2807    // Ensure that comment continuations can be disabled.
 2808    update_test_language_settings(cx, |settings| {
 2809        settings.defaults.extend_comment_on_newline = Some(false);
 2810    });
 2811    let mut cx = EditorTestContext::new(cx).await;
 2812    cx.set_state(indoc! {"
 2813        // Fooˇ
 2814    "});
 2815    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2816    cx.assert_editor_state(indoc! {"
 2817        // Foo
 2818        ˇ
 2819    "});
 2820}
 2821
 2822#[gpui::test]
 2823async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2824    init_test(cx, |settings| {
 2825        settings.defaults.tab_size = NonZeroU32::new(4)
 2826    });
 2827
 2828    let language = Arc::new(Language::new(
 2829        LanguageConfig {
 2830            documentation: Some(language::DocumentationConfig {
 2831                start: "/**".into(),
 2832                end: "*/".into(),
 2833                prefix: "* ".into(),
 2834                tab_size: NonZeroU32::new(1).unwrap(),
 2835            }),
 2836            ..LanguageConfig::default()
 2837        },
 2838        None,
 2839    ));
 2840    {
 2841        let mut cx = EditorTestContext::new(cx).await;
 2842        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2843        cx.set_state(indoc! {"
 2844        /**ˇ
 2845    "});
 2846
 2847        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2848        cx.assert_editor_state(indoc! {"
 2849        /**
 2850         * ˇ
 2851    "});
 2852        // Ensure that if cursor is before the comment start,
 2853        // we do not actually insert a comment prefix.
 2854        cx.set_state(indoc! {"
 2855        ˇ/**
 2856    "});
 2857        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2858        cx.assert_editor_state(indoc! {"
 2859
 2860        ˇ/**
 2861    "});
 2862        // Ensure that if cursor is between it doesn't add comment prefix.
 2863        cx.set_state(indoc! {"
 2864        /*ˇ*
 2865    "});
 2866        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2867        cx.assert_editor_state(indoc! {"
 2868        /*
 2869        ˇ*
 2870    "});
 2871        // Ensure that if suffix exists on same line after cursor it adds new line.
 2872        cx.set_state(indoc! {"
 2873        /**ˇ*/
 2874    "});
 2875        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2876        cx.assert_editor_state(indoc! {"
 2877        /**
 2878         * ˇ
 2879         */
 2880    "});
 2881        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2882        cx.set_state(indoc! {"
 2883        /**ˇ */
 2884    "});
 2885        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2886        cx.assert_editor_state(indoc! {"
 2887        /**
 2888         * ˇ
 2889         */
 2890    "});
 2891        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2892        cx.set_state(indoc! {"
 2893        /** ˇ*/
 2894    "});
 2895        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2896        cx.assert_editor_state(
 2897            indoc! {"
 2898        /**s
 2899         * ˇ
 2900         */
 2901    "}
 2902            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2903            .as_str(),
 2904        );
 2905        // Ensure that delimiter space is preserved when newline on already
 2906        // spaced delimiter.
 2907        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2908        cx.assert_editor_state(
 2909            indoc! {"
 2910        /**s
 2911         *s
 2912         * ˇ
 2913         */
 2914    "}
 2915            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2916            .as_str(),
 2917        );
 2918        // Ensure that delimiter space is preserved when space is not
 2919        // on existing delimiter.
 2920        cx.set_state(indoc! {"
 2921        /**
 2922 2923         */
 2924    "});
 2925        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2926        cx.assert_editor_state(indoc! {"
 2927        /**
 2928         *
 2929         * ˇ
 2930         */
 2931    "});
 2932        // Ensure that if suffix exists on same line after cursor it
 2933        // doesn't add extra new line if prefix is not on same line.
 2934        cx.set_state(indoc! {"
 2935        /**
 2936        ˇ*/
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941
 2942        ˇ*/
 2943    "});
 2944        // Ensure that it detects suffix after existing prefix.
 2945        cx.set_state(indoc! {"
 2946        /**ˇ/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(indoc! {"
 2950        /**
 2951        ˇ/
 2952    "});
 2953        // Ensure that if suffix exists on same line before
 2954        // cursor it does not add comment prefix.
 2955        cx.set_state(indoc! {"
 2956        /** */ˇ
 2957    "});
 2958        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2959        cx.assert_editor_state(indoc! {"
 2960        /** */
 2961        ˇ
 2962    "});
 2963        // Ensure that if suffix exists on same line before
 2964        // cursor it does not add comment prefix.
 2965        cx.set_state(indoc! {"
 2966        /**
 2967         *
 2968         */ˇ
 2969    "});
 2970        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2971        cx.assert_editor_state(indoc! {"
 2972        /**
 2973         *
 2974         */
 2975         ˇ
 2976    "});
 2977    }
 2978    // Ensure that comment continuations can be disabled.
 2979    update_test_language_settings(cx, |settings| {
 2980        settings.defaults.extend_comment_on_newline = Some(false);
 2981    });
 2982    let mut cx = EditorTestContext::new(cx).await;
 2983    cx.set_state(indoc! {"
 2984        /**ˇ
 2985    "});
 2986    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2987    cx.assert_editor_state(indoc! {"
 2988        /**
 2989        ˇ
 2990    "});
 2991}
 2992
 2993#[gpui::test]
 2994fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2999        let mut editor = build_editor(buffer.clone(), window, cx);
 3000        editor.change_selections(None, window, cx, |s| {
 3001            s.select_ranges([3..4, 11..12, 19..20])
 3002        });
 3003        editor
 3004    });
 3005
 3006    _ = editor.update(cx, |editor, window, cx| {
 3007        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3008        editor.buffer.update(cx, |buffer, cx| {
 3009            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3010            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3011        });
 3012        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3013
 3014        editor.insert("Z", window, cx);
 3015        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3016
 3017        // The selections are moved after the inserted characters
 3018        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3019    });
 3020}
 3021
 3022#[gpui::test]
 3023async fn test_tab(cx: &mut TestAppContext) {
 3024    init_test(cx, |settings| {
 3025        settings.defaults.tab_size = NonZeroU32::new(3)
 3026    });
 3027
 3028    let mut cx = EditorTestContext::new(cx).await;
 3029    cx.set_state(indoc! {"
 3030        ˇabˇc
 3031        ˇ🏀ˇ🏀ˇefg
 3032 3033    "});
 3034    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3035    cx.assert_editor_state(indoc! {"
 3036           ˇab ˇc
 3037           ˇ🏀  ˇ🏀  ˇefg
 3038        d  ˇ
 3039    "});
 3040
 3041    cx.set_state(indoc! {"
 3042        a
 3043        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3044    "});
 3045    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3046    cx.assert_editor_state(indoc! {"
 3047        a
 3048           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3049    "});
 3050}
 3051
 3052#[gpui::test]
 3053async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3054    init_test(cx, |_| {});
 3055
 3056    let mut cx = EditorTestContext::new(cx).await;
 3057    let language = Arc::new(
 3058        Language::new(
 3059            LanguageConfig::default(),
 3060            Some(tree_sitter_rust::LANGUAGE.into()),
 3061        )
 3062        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3063        .unwrap(),
 3064    );
 3065    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3066
 3067    // test when all cursors are not at suggested indent
 3068    // then simply move to their suggested indent location
 3069    cx.set_state(indoc! {"
 3070        const a: B = (
 3071            c(
 3072        ˇ
 3073        ˇ    )
 3074        );
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        const a: B = (
 3079            c(
 3080                ˇ
 3081            ˇ)
 3082        );
 3083    "});
 3084
 3085    // test cursor already at suggested indent not moving when
 3086    // other cursors are yet to reach their suggested indents
 3087    cx.set_state(indoc! {"
 3088        ˇ
 3089        const a: B = (
 3090            c(
 3091                d(
 3092        ˇ
 3093                )
 3094        ˇ
 3095        ˇ    )
 3096        );
 3097    "});
 3098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3099    cx.assert_editor_state(indoc! {"
 3100        ˇ
 3101        const a: B = (
 3102            c(
 3103                d(
 3104                    ˇ
 3105                )
 3106                ˇ
 3107            ˇ)
 3108        );
 3109    "});
 3110    // test when all cursors are at suggested indent then tab is inserted
 3111    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3112    cx.assert_editor_state(indoc! {"
 3113            ˇ
 3114        const a: B = (
 3115            c(
 3116                d(
 3117                        ˇ
 3118                )
 3119                    ˇ
 3120                ˇ)
 3121        );
 3122    "});
 3123
 3124    // test when current indent is less than suggested indent,
 3125    // we adjust line to match suggested indent and move cursor to it
 3126    //
 3127    // when no other cursor is at word boundary, all of them should move
 3128    cx.set_state(indoc! {"
 3129        const a: B = (
 3130            c(
 3131                d(
 3132        ˇ
 3133        ˇ   )
 3134        ˇ   )
 3135        );
 3136    "});
 3137    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3138    cx.assert_editor_state(indoc! {"
 3139        const a: B = (
 3140            c(
 3141                d(
 3142                    ˇ
 3143                ˇ)
 3144            ˇ)
 3145        );
 3146    "});
 3147
 3148    // test when current indent is less than suggested indent,
 3149    // we adjust line to match suggested indent and move cursor to it
 3150    //
 3151    // when some other cursor is at word boundary, it should not move
 3152    cx.set_state(indoc! {"
 3153        const a: B = (
 3154            c(
 3155                d(
 3156        ˇ
 3157        ˇ   )
 3158           ˇ)
 3159        );
 3160    "});
 3161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3162    cx.assert_editor_state(indoc! {"
 3163        const a: B = (
 3164            c(
 3165                d(
 3166                    ˇ
 3167                ˇ)
 3168            ˇ)
 3169        );
 3170    "});
 3171
 3172    // test when current indent is more than suggested indent,
 3173    // we just move cursor to current indent instead of suggested indent
 3174    //
 3175    // when no other cursor is at word boundary, all of them should move
 3176    cx.set_state(indoc! {"
 3177        const a: B = (
 3178            c(
 3179                d(
 3180        ˇ
 3181        ˇ                )
 3182        ˇ   )
 3183        );
 3184    "});
 3185    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3186    cx.assert_editor_state(indoc! {"
 3187        const a: B = (
 3188            c(
 3189                d(
 3190                    ˇ
 3191                        ˇ)
 3192            ˇ)
 3193        );
 3194    "});
 3195    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3196    cx.assert_editor_state(indoc! {"
 3197        const a: B = (
 3198            c(
 3199                d(
 3200                        ˇ
 3201                            ˇ)
 3202                ˇ)
 3203        );
 3204    "});
 3205
 3206    // test when current indent is more than suggested indent,
 3207    // we just move cursor to current indent instead of suggested indent
 3208    //
 3209    // when some other cursor is at word boundary, it doesn't move
 3210    cx.set_state(indoc! {"
 3211        const a: B = (
 3212            c(
 3213                d(
 3214        ˇ
 3215        ˇ                )
 3216            ˇ)
 3217        );
 3218    "});
 3219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221        const a: B = (
 3222            c(
 3223                d(
 3224                    ˇ
 3225                        ˇ)
 3226            ˇ)
 3227        );
 3228    "});
 3229
 3230    // handle auto-indent when there are multiple cursors on the same line
 3231    cx.set_state(indoc! {"
 3232        const a: B = (
 3233            c(
 3234        ˇ    ˇ
 3235        ˇ    )
 3236        );
 3237    "});
 3238    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3239    cx.assert_editor_state(indoc! {"
 3240        const a: B = (
 3241            c(
 3242                ˇ
 3243            ˇ)
 3244        );
 3245    "});
 3246}
 3247
 3248#[gpui::test]
 3249async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3250    init_test(cx, |settings| {
 3251        settings.defaults.tab_size = NonZeroU32::new(3)
 3252    });
 3253
 3254    let mut cx = EditorTestContext::new(cx).await;
 3255    cx.set_state(indoc! {"
 3256         ˇ
 3257        \t ˇ
 3258        \t  ˇ
 3259        \t   ˇ
 3260         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3261    "});
 3262
 3263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3264    cx.assert_editor_state(indoc! {"
 3265           ˇ
 3266        \t   ˇ
 3267        \t   ˇ
 3268        \t      ˇ
 3269         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3270    "});
 3271}
 3272
 3273#[gpui::test]
 3274async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3275    init_test(cx, |settings| {
 3276        settings.defaults.tab_size = NonZeroU32::new(4)
 3277    });
 3278
 3279    let language = Arc::new(
 3280        Language::new(
 3281            LanguageConfig::default(),
 3282            Some(tree_sitter_rust::LANGUAGE.into()),
 3283        )
 3284        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3285        .unwrap(),
 3286    );
 3287
 3288    let mut cx = EditorTestContext::new(cx).await;
 3289    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3290    cx.set_state(indoc! {"
 3291        fn a() {
 3292            if b {
 3293        \t ˇc
 3294            }
 3295        }
 3296    "});
 3297
 3298    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3299    cx.assert_editor_state(indoc! {"
 3300        fn a() {
 3301            if b {
 3302                ˇc
 3303            }
 3304        }
 3305    "});
 3306}
 3307
 3308#[gpui::test]
 3309async fn test_indent_outdent(cx: &mut TestAppContext) {
 3310    init_test(cx, |settings| {
 3311        settings.defaults.tab_size = NonZeroU32::new(4);
 3312    });
 3313
 3314    let mut cx = EditorTestContext::new(cx).await;
 3315
 3316    cx.set_state(indoc! {"
 3317          «oneˇ» «twoˇ»
 3318        three
 3319         four
 3320    "});
 3321    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3322    cx.assert_editor_state(indoc! {"
 3323            «oneˇ» «twoˇ»
 3324        three
 3325         four
 3326    "});
 3327
 3328    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        «oneˇ» «twoˇ»
 3331        three
 3332         four
 3333    "});
 3334
 3335    // select across line ending
 3336    cx.set_state(indoc! {"
 3337        one two
 3338        t«hree
 3339        ˇ» four
 3340    "});
 3341    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3342    cx.assert_editor_state(indoc! {"
 3343        one two
 3344            t«hree
 3345        ˇ» four
 3346    "});
 3347
 3348    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3349    cx.assert_editor_state(indoc! {"
 3350        one two
 3351        t«hree
 3352        ˇ» four
 3353    "});
 3354
 3355    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3356    cx.set_state(indoc! {"
 3357        one two
 3358        ˇthree
 3359            four
 3360    "});
 3361    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3362    cx.assert_editor_state(indoc! {"
 3363        one two
 3364            ˇthree
 3365            four
 3366    "});
 3367
 3368    cx.set_state(indoc! {"
 3369        one two
 3370        ˇ    three
 3371            four
 3372    "});
 3373    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3374    cx.assert_editor_state(indoc! {"
 3375        one two
 3376        ˇthree
 3377            four
 3378    "});
 3379}
 3380
 3381#[gpui::test]
 3382async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3383    init_test(cx, |settings| {
 3384        settings.defaults.hard_tabs = Some(true);
 3385    });
 3386
 3387    let mut cx = EditorTestContext::new(cx).await;
 3388
 3389    // select two ranges on one line
 3390    cx.set_state(indoc! {"
 3391        «oneˇ» «twoˇ»
 3392        three
 3393        four
 3394    "});
 3395    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3396    cx.assert_editor_state(indoc! {"
 3397        \t«oneˇ» «twoˇ»
 3398        three
 3399        four
 3400    "});
 3401    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3402    cx.assert_editor_state(indoc! {"
 3403        \t\t«oneˇ» «twoˇ»
 3404        three
 3405        four
 3406    "});
 3407    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3408    cx.assert_editor_state(indoc! {"
 3409        \t«oneˇ» «twoˇ»
 3410        three
 3411        four
 3412    "});
 3413    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3414    cx.assert_editor_state(indoc! {"
 3415        «oneˇ» «twoˇ»
 3416        three
 3417        four
 3418    "});
 3419
 3420    // select across a line ending
 3421    cx.set_state(indoc! {"
 3422        one two
 3423        t«hree
 3424        ˇ»four
 3425    "});
 3426    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3427    cx.assert_editor_state(indoc! {"
 3428        one two
 3429        \tt«hree
 3430        ˇ»four
 3431    "});
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        one two
 3435        \t\tt«hree
 3436        ˇ»four
 3437    "});
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        \tt«hree
 3442        ˇ»four
 3443    "});
 3444    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3445    cx.assert_editor_state(indoc! {"
 3446        one two
 3447        t«hree
 3448        ˇ»four
 3449    "});
 3450
 3451    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3452    cx.set_state(indoc! {"
 3453        one two
 3454        ˇthree
 3455        four
 3456    "});
 3457    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        one two
 3460        ˇthree
 3461        four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        \tˇthree
 3467        four
 3468    "});
 3469    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3470    cx.assert_editor_state(indoc! {"
 3471        one two
 3472        ˇthree
 3473        four
 3474    "});
 3475}
 3476
 3477#[gpui::test]
 3478fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3479    init_test(cx, |settings| {
 3480        settings.languages.extend([
 3481            (
 3482                "TOML".into(),
 3483                LanguageSettingsContent {
 3484                    tab_size: NonZeroU32::new(2),
 3485                    ..Default::default()
 3486                },
 3487            ),
 3488            (
 3489                "Rust".into(),
 3490                LanguageSettingsContent {
 3491                    tab_size: NonZeroU32::new(4),
 3492                    ..Default::default()
 3493                },
 3494            ),
 3495        ]);
 3496    });
 3497
 3498    let toml_language = Arc::new(Language::new(
 3499        LanguageConfig {
 3500            name: "TOML".into(),
 3501            ..Default::default()
 3502        },
 3503        None,
 3504    ));
 3505    let rust_language = Arc::new(Language::new(
 3506        LanguageConfig {
 3507            name: "Rust".into(),
 3508            ..Default::default()
 3509        },
 3510        None,
 3511    ));
 3512
 3513    let toml_buffer =
 3514        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3515    let rust_buffer =
 3516        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3517    let multibuffer = cx.new(|cx| {
 3518        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3519        multibuffer.push_excerpts(
 3520            toml_buffer.clone(),
 3521            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3522            cx,
 3523        );
 3524        multibuffer.push_excerpts(
 3525            rust_buffer.clone(),
 3526            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3527            cx,
 3528        );
 3529        multibuffer
 3530    });
 3531
 3532    cx.add_window(|window, cx| {
 3533        let mut editor = build_editor(multibuffer, window, cx);
 3534
 3535        assert_eq!(
 3536            editor.text(cx),
 3537            indoc! {"
 3538                a = 1
 3539                b = 2
 3540
 3541                const c: usize = 3;
 3542            "}
 3543        );
 3544
 3545        select_ranges(
 3546            &mut editor,
 3547            indoc! {"
 3548                «aˇ» = 1
 3549                b = 2
 3550
 3551                «const c:ˇ» usize = 3;
 3552            "},
 3553            window,
 3554            cx,
 3555        );
 3556
 3557        editor.tab(&Tab, window, cx);
 3558        assert_text_with_selections(
 3559            &mut editor,
 3560            indoc! {"
 3561                  «aˇ» = 1
 3562                b = 2
 3563
 3564                    «const c:ˇ» usize = 3;
 3565            "},
 3566            cx,
 3567        );
 3568        editor.backtab(&Backtab, window, cx);
 3569        assert_text_with_selections(
 3570            &mut editor,
 3571            indoc! {"
 3572                «aˇ» = 1
 3573                b = 2
 3574
 3575                «const c:ˇ» usize = 3;
 3576            "},
 3577            cx,
 3578        );
 3579
 3580        editor
 3581    });
 3582}
 3583
 3584#[gpui::test]
 3585async fn test_backspace(cx: &mut TestAppContext) {
 3586    init_test(cx, |_| {});
 3587
 3588    let mut cx = EditorTestContext::new(cx).await;
 3589
 3590    // Basic backspace
 3591    cx.set_state(indoc! {"
 3592        onˇe two three
 3593        fou«rˇ» five six
 3594        seven «ˇeight nine
 3595        »ten
 3596    "});
 3597    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3598    cx.assert_editor_state(indoc! {"
 3599        oˇe two three
 3600        fouˇ five six
 3601        seven ˇten
 3602    "});
 3603
 3604    // Test backspace inside and around indents
 3605    cx.set_state(indoc! {"
 3606        zero
 3607            ˇone
 3608                ˇtwo
 3609            ˇ ˇ ˇ  three
 3610        ˇ  ˇ  four
 3611    "});
 3612    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3613    cx.assert_editor_state(indoc! {"
 3614        zero
 3615        ˇone
 3616            ˇtwo
 3617        ˇ  threeˇ  four
 3618    "});
 3619}
 3620
 3621#[gpui::test]
 3622async fn test_delete(cx: &mut TestAppContext) {
 3623    init_test(cx, |_| {});
 3624
 3625    let mut cx = EditorTestContext::new(cx).await;
 3626    cx.set_state(indoc! {"
 3627        onˇe two three
 3628        fou«rˇ» five six
 3629        seven «ˇeight nine
 3630        »ten
 3631    "});
 3632    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634        onˇ two three
 3635        fouˇ five six
 3636        seven ˇten
 3637    "});
 3638}
 3639
 3640#[gpui::test]
 3641fn test_delete_line(cx: &mut TestAppContext) {
 3642    init_test(cx, |_| {});
 3643
 3644    let editor = cx.add_window(|window, cx| {
 3645        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3646        build_editor(buffer, window, cx)
 3647    });
 3648    _ = editor.update(cx, |editor, window, cx| {
 3649        editor.change_selections(None, window, cx, |s| {
 3650            s.select_display_ranges([
 3651                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3652                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3653                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3654            ])
 3655        });
 3656        editor.delete_line(&DeleteLine, window, cx);
 3657        assert_eq!(editor.display_text(cx), "ghi");
 3658        assert_eq!(
 3659            editor.selections.display_ranges(cx),
 3660            vec![
 3661                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3662                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3663            ]
 3664        );
 3665    });
 3666
 3667    let editor = cx.add_window(|window, cx| {
 3668        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3669        build_editor(buffer, window, cx)
 3670    });
 3671    _ = editor.update(cx, |editor, window, cx| {
 3672        editor.change_selections(None, window, cx, |s| {
 3673            s.select_display_ranges([
 3674                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3675            ])
 3676        });
 3677        editor.delete_line(&DeleteLine, window, cx);
 3678        assert_eq!(editor.display_text(cx), "ghi\n");
 3679        assert_eq!(
 3680            editor.selections.display_ranges(cx),
 3681            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3682        );
 3683    });
 3684}
 3685
 3686#[gpui::test]
 3687fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3688    init_test(cx, |_| {});
 3689
 3690    cx.add_window(|window, cx| {
 3691        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3692        let mut editor = build_editor(buffer.clone(), window, cx);
 3693        let buffer = buffer.read(cx).as_singleton().unwrap();
 3694
 3695        assert_eq!(
 3696            editor.selections.ranges::<Point>(cx),
 3697            &[Point::new(0, 0)..Point::new(0, 0)]
 3698        );
 3699
 3700        // When on single line, replace newline at end by space
 3701        editor.join_lines(&JoinLines, window, cx);
 3702        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3703        assert_eq!(
 3704            editor.selections.ranges::<Point>(cx),
 3705            &[Point::new(0, 3)..Point::new(0, 3)]
 3706        );
 3707
 3708        // When multiple lines are selected, remove newlines that are spanned by the selection
 3709        editor.change_selections(None, window, cx, |s| {
 3710            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3711        });
 3712        editor.join_lines(&JoinLines, window, cx);
 3713        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3714        assert_eq!(
 3715            editor.selections.ranges::<Point>(cx),
 3716            &[Point::new(0, 11)..Point::new(0, 11)]
 3717        );
 3718
 3719        // Undo should be transactional
 3720        editor.undo(&Undo, window, cx);
 3721        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3722        assert_eq!(
 3723            editor.selections.ranges::<Point>(cx),
 3724            &[Point::new(0, 5)..Point::new(2, 2)]
 3725        );
 3726
 3727        // When joining an empty line don't insert a space
 3728        editor.change_selections(None, window, cx, |s| {
 3729            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3730        });
 3731        editor.join_lines(&JoinLines, window, cx);
 3732        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3733        assert_eq!(
 3734            editor.selections.ranges::<Point>(cx),
 3735            [Point::new(2, 3)..Point::new(2, 3)]
 3736        );
 3737
 3738        // We can remove trailing newlines
 3739        editor.join_lines(&JoinLines, window, cx);
 3740        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3741        assert_eq!(
 3742            editor.selections.ranges::<Point>(cx),
 3743            [Point::new(2, 3)..Point::new(2, 3)]
 3744        );
 3745
 3746        // We don't blow up on the last line
 3747        editor.join_lines(&JoinLines, window, cx);
 3748        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3749        assert_eq!(
 3750            editor.selections.ranges::<Point>(cx),
 3751            [Point::new(2, 3)..Point::new(2, 3)]
 3752        );
 3753
 3754        // reset to test indentation
 3755        editor.buffer.update(cx, |buffer, cx| {
 3756            buffer.edit(
 3757                [
 3758                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3759                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3760                ],
 3761                None,
 3762                cx,
 3763            )
 3764        });
 3765
 3766        // We remove any leading spaces
 3767        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3768        editor.change_selections(None, window, cx, |s| {
 3769            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3770        });
 3771        editor.join_lines(&JoinLines, window, cx);
 3772        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3773
 3774        // We don't insert a space for a line containing only spaces
 3775        editor.join_lines(&JoinLines, window, cx);
 3776        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3777
 3778        // We ignore any leading tabs
 3779        editor.join_lines(&JoinLines, window, cx);
 3780        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3781
 3782        editor
 3783    });
 3784}
 3785
 3786#[gpui::test]
 3787fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3788    init_test(cx, |_| {});
 3789
 3790    cx.add_window(|window, cx| {
 3791        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3792        let mut editor = build_editor(buffer.clone(), window, cx);
 3793        let buffer = buffer.read(cx).as_singleton().unwrap();
 3794
 3795        editor.change_selections(None, window, cx, |s| {
 3796            s.select_ranges([
 3797                Point::new(0, 2)..Point::new(1, 1),
 3798                Point::new(1, 2)..Point::new(1, 2),
 3799                Point::new(3, 1)..Point::new(3, 2),
 3800            ])
 3801        });
 3802
 3803        editor.join_lines(&JoinLines, window, cx);
 3804        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3805
 3806        assert_eq!(
 3807            editor.selections.ranges::<Point>(cx),
 3808            [
 3809                Point::new(0, 7)..Point::new(0, 7),
 3810                Point::new(1, 3)..Point::new(1, 3)
 3811            ]
 3812        );
 3813        editor
 3814    });
 3815}
 3816
 3817#[gpui::test]
 3818async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3819    init_test(cx, |_| {});
 3820
 3821    let mut cx = EditorTestContext::new(cx).await;
 3822
 3823    let diff_base = r#"
 3824        Line 0
 3825        Line 1
 3826        Line 2
 3827        Line 3
 3828        "#
 3829    .unindent();
 3830
 3831    cx.set_state(
 3832        &r#"
 3833        ˇLine 0
 3834        Line 1
 3835        Line 2
 3836        Line 3
 3837        "#
 3838        .unindent(),
 3839    );
 3840
 3841    cx.set_head_text(&diff_base);
 3842    executor.run_until_parked();
 3843
 3844    // Join lines
 3845    cx.update_editor(|editor, window, cx| {
 3846        editor.join_lines(&JoinLines, window, cx);
 3847    });
 3848    executor.run_until_parked();
 3849
 3850    cx.assert_editor_state(
 3851        &r#"
 3852        Line 0ˇ Line 1
 3853        Line 2
 3854        Line 3
 3855        "#
 3856        .unindent(),
 3857    );
 3858    // Join again
 3859    cx.update_editor(|editor, window, cx| {
 3860        editor.join_lines(&JoinLines, window, cx);
 3861    });
 3862    executor.run_until_parked();
 3863
 3864    cx.assert_editor_state(
 3865        &r#"
 3866        Line 0 Line 1ˇ Line 2
 3867        Line 3
 3868        "#
 3869        .unindent(),
 3870    );
 3871}
 3872
 3873#[gpui::test]
 3874async fn test_custom_newlines_cause_no_false_positive_diffs(
 3875    executor: BackgroundExecutor,
 3876    cx: &mut TestAppContext,
 3877) {
 3878    init_test(cx, |_| {});
 3879    let mut cx = EditorTestContext::new(cx).await;
 3880    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3881    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3882    executor.run_until_parked();
 3883
 3884    cx.update_editor(|editor, window, cx| {
 3885        let snapshot = editor.snapshot(window, cx);
 3886        assert_eq!(
 3887            snapshot
 3888                .buffer_snapshot
 3889                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3890                .collect::<Vec<_>>(),
 3891            Vec::new(),
 3892            "Should not have any diffs for files with custom newlines"
 3893        );
 3894    });
 3895}
 3896
 3897#[gpui::test]
 3898async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3899    init_test(cx, |_| {});
 3900
 3901    let mut cx = EditorTestContext::new(cx).await;
 3902
 3903    // Test sort_lines_case_insensitive()
 3904    cx.set_state(indoc! {"
 3905        «z
 3906        y
 3907        x
 3908        Z
 3909        Y
 3910        Xˇ»
 3911    "});
 3912    cx.update_editor(|e, window, cx| {
 3913        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3914    });
 3915    cx.assert_editor_state(indoc! {"
 3916        «x
 3917        X
 3918        y
 3919        Y
 3920        z
 3921        Zˇ»
 3922    "});
 3923
 3924    // Test reverse_lines()
 3925    cx.set_state(indoc! {"
 3926        «5
 3927        4
 3928        3
 3929        2
 3930        1ˇ»
 3931    "});
 3932    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3933    cx.assert_editor_state(indoc! {"
 3934        «1
 3935        2
 3936        3
 3937        4
 3938        5ˇ»
 3939    "});
 3940
 3941    // Skip testing shuffle_line()
 3942
 3943    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3944    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3945
 3946    // Don't manipulate when cursor is on single line, but expand the selection
 3947    cx.set_state(indoc! {"
 3948        ddˇdd
 3949        ccc
 3950        bb
 3951        a
 3952    "});
 3953    cx.update_editor(|e, window, cx| {
 3954        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3955    });
 3956    cx.assert_editor_state(indoc! {"
 3957        «ddddˇ»
 3958        ccc
 3959        bb
 3960        a
 3961    "});
 3962
 3963    // Basic manipulate case
 3964    // Start selection moves to column 0
 3965    // End of selection shrinks to fit shorter line
 3966    cx.set_state(indoc! {"
 3967        dd«d
 3968        ccc
 3969        bb
 3970        aaaaaˇ»
 3971    "});
 3972    cx.update_editor(|e, window, cx| {
 3973        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3974    });
 3975    cx.assert_editor_state(indoc! {"
 3976        «aaaaa
 3977        bb
 3978        ccc
 3979        dddˇ»
 3980    "});
 3981
 3982    // Manipulate case with newlines
 3983    cx.set_state(indoc! {"
 3984        dd«d
 3985        ccc
 3986
 3987        bb
 3988        aaaaa
 3989
 3990        ˇ»
 3991    "});
 3992    cx.update_editor(|e, window, cx| {
 3993        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3994    });
 3995    cx.assert_editor_state(indoc! {"
 3996        «
 3997
 3998        aaaaa
 3999        bb
 4000        ccc
 4001        dddˇ»
 4002
 4003    "});
 4004
 4005    // Adding new line
 4006    cx.set_state(indoc! {"
 4007        aa«a
 4008        bbˇ»b
 4009    "});
 4010    cx.update_editor(|e, window, cx| {
 4011        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4012    });
 4013    cx.assert_editor_state(indoc! {"
 4014        «aaa
 4015        bbb
 4016        added_lineˇ»
 4017    "});
 4018
 4019    // Removing line
 4020    cx.set_state(indoc! {"
 4021        aa«a
 4022        bbbˇ»
 4023    "});
 4024    cx.update_editor(|e, window, cx| {
 4025        e.manipulate_lines(window, cx, |lines| {
 4026            lines.pop();
 4027        })
 4028    });
 4029    cx.assert_editor_state(indoc! {"
 4030        «aaaˇ»
 4031    "});
 4032
 4033    // Removing all lines
 4034    cx.set_state(indoc! {"
 4035        aa«a
 4036        bbbˇ»
 4037    "});
 4038    cx.update_editor(|e, window, cx| {
 4039        e.manipulate_lines(window, cx, |lines| {
 4040            lines.drain(..);
 4041        })
 4042    });
 4043    cx.assert_editor_state(indoc! {"
 4044        ˇ
 4045    "});
 4046}
 4047
 4048#[gpui::test]
 4049async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4050    init_test(cx, |_| {});
 4051
 4052    let mut cx = EditorTestContext::new(cx).await;
 4053
 4054    // Consider continuous selection as single selection
 4055    cx.set_state(indoc! {"
 4056        Aaa«aa
 4057        cˇ»c«c
 4058        bb
 4059        aaaˇ»aa
 4060    "});
 4061    cx.update_editor(|e, window, cx| {
 4062        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4063    });
 4064    cx.assert_editor_state(indoc! {"
 4065        «Aaaaa
 4066        ccc
 4067        bb
 4068        aaaaaˇ»
 4069    "});
 4070
 4071    cx.set_state(indoc! {"
 4072        Aaa«aa
 4073        cˇ»c«c
 4074        bb
 4075        aaaˇ»aa
 4076    "});
 4077    cx.update_editor(|e, window, cx| {
 4078        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4079    });
 4080    cx.assert_editor_state(indoc! {"
 4081        «Aaaaa
 4082        ccc
 4083        bbˇ»
 4084    "});
 4085
 4086    // Consider non continuous selection as distinct dedup operations
 4087    cx.set_state(indoc! {"
 4088        «aaaaa
 4089        bb
 4090        aaaaa
 4091        aaaaaˇ»
 4092
 4093        aaa«aaˇ»
 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        «aaaaa
 4100        bbˇ»
 4101
 4102        «aaaaaˇ»
 4103    "});
 4104}
 4105
 4106#[gpui::test]
 4107async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4108    init_test(cx, |_| {});
 4109
 4110    let mut cx = EditorTestContext::new(cx).await;
 4111
 4112    cx.set_state(indoc! {"
 4113        «Aaa
 4114        aAa
 4115        Aaaˇ»
 4116    "});
 4117    cx.update_editor(|e, window, cx| {
 4118        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4119    });
 4120    cx.assert_editor_state(indoc! {"
 4121        «Aaa
 4122        aAaˇ»
 4123    "});
 4124
 4125    cx.set_state(indoc! {"
 4126        «Aaa
 4127        aAa
 4128        aaAˇ»
 4129    "});
 4130    cx.update_editor(|e, window, cx| {
 4131        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        «Aaaˇ»
 4135    "});
 4136}
 4137
 4138#[gpui::test]
 4139async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4140    init_test(cx, |_| {});
 4141
 4142    let mut cx = EditorTestContext::new(cx).await;
 4143
 4144    // Manipulate with multiple selections on a single line
 4145    cx.set_state(indoc! {"
 4146        dd«dd
 4147        cˇ»c«c
 4148        bb
 4149        aaaˇ»aa
 4150    "});
 4151    cx.update_editor(|e, window, cx| {
 4152        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4153    });
 4154    cx.assert_editor_state(indoc! {"
 4155        «aaaaa
 4156        bb
 4157        ccc
 4158        ddddˇ»
 4159    "});
 4160
 4161    // Manipulate with multiple disjoin selections
 4162    cx.set_state(indoc! {"
 4163 4164        4
 4165        3
 4166        2
 4167        1ˇ»
 4168
 4169        dd«dd
 4170        ccc
 4171        bb
 4172        aaaˇ»aa
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «1
 4179        2
 4180        3
 4181        4
 4182        5ˇ»
 4183
 4184        «aaaaa
 4185        bb
 4186        ccc
 4187        ddddˇ»
 4188    "});
 4189
 4190    // Adding lines on each selection
 4191    cx.set_state(indoc! {"
 4192 4193        1ˇ»
 4194
 4195        bb«bb
 4196        aaaˇ»aa
 4197    "});
 4198    cx.update_editor(|e, window, cx| {
 4199        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4200    });
 4201    cx.assert_editor_state(indoc! {"
 4202        «2
 4203        1
 4204        added lineˇ»
 4205
 4206        «bbbb
 4207        aaaaa
 4208        added lineˇ»
 4209    "});
 4210
 4211    // Removing lines on each selection
 4212    cx.set_state(indoc! {"
 4213 4214        1ˇ»
 4215
 4216        bb«bb
 4217        aaaˇ»aa
 4218    "});
 4219    cx.update_editor(|e, window, cx| {
 4220        e.manipulate_lines(window, cx, |lines| {
 4221            lines.pop();
 4222        })
 4223    });
 4224    cx.assert_editor_state(indoc! {"
 4225        «2ˇ»
 4226
 4227        «bbbbˇ»
 4228    "});
 4229}
 4230
 4231#[gpui::test]
 4232async fn test_toggle_case(cx: &mut TestAppContext) {
 4233    init_test(cx, |_| {});
 4234
 4235    let mut cx = EditorTestContext::new(cx).await;
 4236
 4237    // If all lower case -> upper case
 4238    cx.set_state(indoc! {"
 4239        «hello worldˇ»
 4240    "});
 4241    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4242    cx.assert_editor_state(indoc! {"
 4243        «HELLO WORLDˇ»
 4244    "});
 4245
 4246    // If all upper case -> lower case
 4247    cx.set_state(indoc! {"
 4248        «HELLO WORLDˇ»
 4249    "});
 4250    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4251    cx.assert_editor_state(indoc! {"
 4252        «hello worldˇ»
 4253    "});
 4254
 4255    // If any upper case characters are identified -> lower case
 4256    // This matches JetBrains IDEs
 4257    cx.set_state(indoc! {"
 4258        «hEllo worldˇ»
 4259    "});
 4260    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4261    cx.assert_editor_state(indoc! {"
 4262        «hello worldˇ»
 4263    "});
 4264}
 4265
 4266#[gpui::test]
 4267async fn test_manipulate_text(cx: &mut TestAppContext) {
 4268    init_test(cx, |_| {});
 4269
 4270    let mut cx = EditorTestContext::new(cx).await;
 4271
 4272    // Test convert_to_upper_case()
 4273    cx.set_state(indoc! {"
 4274        «hello worldˇ»
 4275    "});
 4276    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4277    cx.assert_editor_state(indoc! {"
 4278        «HELLO WORLDˇ»
 4279    "});
 4280
 4281    // Test convert_to_lower_case()
 4282    cx.set_state(indoc! {"
 4283        «HELLO WORLDˇ»
 4284    "});
 4285    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4286    cx.assert_editor_state(indoc! {"
 4287        «hello worldˇ»
 4288    "});
 4289
 4290    // Test multiple line, single selection case
 4291    cx.set_state(indoc! {"
 4292        «The quick brown
 4293        fox jumps over
 4294        the lazy dogˇ»
 4295    "});
 4296    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4297    cx.assert_editor_state(indoc! {"
 4298        «The Quick Brown
 4299        Fox Jumps Over
 4300        The Lazy Dogˇ»
 4301    "});
 4302
 4303    // Test multiple line, single selection case
 4304    cx.set_state(indoc! {"
 4305        «The quick brown
 4306        fox jumps over
 4307        the lazy dogˇ»
 4308    "});
 4309    cx.update_editor(|e, window, cx| {
 4310        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4311    });
 4312    cx.assert_editor_state(indoc! {"
 4313        «TheQuickBrown
 4314        FoxJumpsOver
 4315        TheLazyDogˇ»
 4316    "});
 4317
 4318    // From here on out, test more complex cases of manipulate_text()
 4319
 4320    // Test no selection case - should affect words cursors are in
 4321    // Cursor at beginning, middle, and end of word
 4322    cx.set_state(indoc! {"
 4323        ˇhello big beauˇtiful worldˇ
 4324    "});
 4325    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4326    cx.assert_editor_state(indoc! {"
 4327        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4328    "});
 4329
 4330    // Test multiple selections on a single line and across multiple lines
 4331    cx.set_state(indoc! {"
 4332        «Theˇ» quick «brown
 4333        foxˇ» jumps «overˇ»
 4334        the «lazyˇ» dog
 4335    "});
 4336    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4337    cx.assert_editor_state(indoc! {"
 4338        «THEˇ» quick «BROWN
 4339        FOXˇ» jumps «OVERˇ»
 4340        the «LAZYˇ» dog
 4341    "});
 4342
 4343    // Test case where text length grows
 4344    cx.set_state(indoc! {"
 4345        «tschüߡ»
 4346    "});
 4347    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4348    cx.assert_editor_state(indoc! {"
 4349        «TSCHÜSSˇ»
 4350    "});
 4351
 4352    // Test to make sure we don't crash when text shrinks
 4353    cx.set_state(indoc! {"
 4354        aaa_bbbˇ
 4355    "});
 4356    cx.update_editor(|e, window, cx| {
 4357        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4358    });
 4359    cx.assert_editor_state(indoc! {"
 4360        «aaaBbbˇ»
 4361    "});
 4362
 4363    // Test to make sure we all aware of the fact that each word can grow and shrink
 4364    // Final selections should be aware of this fact
 4365    cx.set_state(indoc! {"
 4366        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4367    "});
 4368    cx.update_editor(|e, window, cx| {
 4369        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4370    });
 4371    cx.assert_editor_state(indoc! {"
 4372        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4373    "});
 4374
 4375    cx.set_state(indoc! {"
 4376        «hElLo, WoRld!ˇ»
 4377    "});
 4378    cx.update_editor(|e, window, cx| {
 4379        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4380    });
 4381    cx.assert_editor_state(indoc! {"
 4382        «HeLlO, wOrLD!ˇ»
 4383    "});
 4384}
 4385
 4386#[gpui::test]
 4387fn test_duplicate_line(cx: &mut TestAppContext) {
 4388    init_test(cx, |_| {});
 4389
 4390    let editor = cx.add_window(|window, cx| {
 4391        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4392        build_editor(buffer, window, cx)
 4393    });
 4394    _ = editor.update(cx, |editor, window, cx| {
 4395        editor.change_selections(None, window, cx, |s| {
 4396            s.select_display_ranges([
 4397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4398                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4399                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4400                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4401            ])
 4402        });
 4403        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4404        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4405        assert_eq!(
 4406            editor.selections.display_ranges(cx),
 4407            vec![
 4408                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4409                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4410                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4411                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4412            ]
 4413        );
 4414    });
 4415
 4416    let editor = cx.add_window(|window, cx| {
 4417        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4418        build_editor(buffer, window, cx)
 4419    });
 4420    _ = editor.update(cx, |editor, window, cx| {
 4421        editor.change_selections(None, window, cx, |s| {
 4422            s.select_display_ranges([
 4423                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4424                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4425            ])
 4426        });
 4427        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4428        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4429        assert_eq!(
 4430            editor.selections.display_ranges(cx),
 4431            vec![
 4432                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4433                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4434            ]
 4435        );
 4436    });
 4437
 4438    // With `move_upwards` the selections stay in place, except for
 4439    // the lines inserted above them
 4440    let editor = cx.add_window(|window, cx| {
 4441        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4442        build_editor(buffer, window, cx)
 4443    });
 4444    _ = editor.update(cx, |editor, window, cx| {
 4445        editor.change_selections(None, window, cx, |s| {
 4446            s.select_display_ranges([
 4447                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4448                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4449                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4450                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4451            ])
 4452        });
 4453        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4454        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4455        assert_eq!(
 4456            editor.selections.display_ranges(cx),
 4457            vec![
 4458                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4459                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4460                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4461                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 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_line_up(&DuplicateLineUp, window, cx);
 4478        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\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(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4484            ]
 4485        );
 4486    });
 4487
 4488    let editor = cx.add_window(|window, cx| {
 4489        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4490        build_editor(buffer, window, cx)
 4491    });
 4492    _ = editor.update(cx, |editor, window, cx| {
 4493        editor.change_selections(None, window, cx, |s| {
 4494            s.select_display_ranges([
 4495                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4496                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4497            ])
 4498        });
 4499        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4500        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4501        assert_eq!(
 4502            editor.selections.display_ranges(cx),
 4503            vec![
 4504                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4505                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4506            ]
 4507        );
 4508    });
 4509}
 4510
 4511#[gpui::test]
 4512fn test_move_line_up_down(cx: &mut TestAppContext) {
 4513    init_test(cx, |_| {});
 4514
 4515    let editor = cx.add_window(|window, cx| {
 4516        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4517        build_editor(buffer, window, cx)
 4518    });
 4519    _ = editor.update(cx, |editor, window, cx| {
 4520        editor.fold_creases(
 4521            vec![
 4522                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4523                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4524                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4525            ],
 4526            true,
 4527            window,
 4528            cx,
 4529        );
 4530        editor.change_selections(None, window, cx, |s| {
 4531            s.select_display_ranges([
 4532                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4533                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4534                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4535                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4536            ])
 4537        });
 4538        assert_eq!(
 4539            editor.display_text(cx),
 4540            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4541        );
 4542
 4543        editor.move_line_up(&MoveLineUp, window, cx);
 4544        assert_eq!(
 4545            editor.display_text(cx),
 4546            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4547        );
 4548        assert_eq!(
 4549            editor.selections.display_ranges(cx),
 4550            vec![
 4551                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4552                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4553                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4554                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4555            ]
 4556        );
 4557    });
 4558
 4559    _ = editor.update(cx, |editor, window, cx| {
 4560        editor.move_line_down(&MoveLineDown, window, cx);
 4561        assert_eq!(
 4562            editor.display_text(cx),
 4563            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4564        );
 4565        assert_eq!(
 4566            editor.selections.display_ranges(cx),
 4567            vec![
 4568                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4569                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4570                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4571                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4572            ]
 4573        );
 4574    });
 4575
 4576    _ = editor.update(cx, |editor, window, cx| {
 4577        editor.move_line_down(&MoveLineDown, window, cx);
 4578        assert_eq!(
 4579            editor.display_text(cx),
 4580            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4581        );
 4582        assert_eq!(
 4583            editor.selections.display_ranges(cx),
 4584            vec![
 4585                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4586                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4587                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4588                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4589            ]
 4590        );
 4591    });
 4592
 4593    _ = editor.update(cx, |editor, window, cx| {
 4594        editor.move_line_up(&MoveLineUp, window, cx);
 4595        assert_eq!(
 4596            editor.display_text(cx),
 4597            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4598        );
 4599        assert_eq!(
 4600            editor.selections.display_ranges(cx),
 4601            vec![
 4602                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4603                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4604                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4605                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4606            ]
 4607        );
 4608    });
 4609}
 4610
 4611#[gpui::test]
 4612fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4613    init_test(cx, |_| {});
 4614
 4615    let editor = cx.add_window(|window, cx| {
 4616        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4617        build_editor(buffer, window, cx)
 4618    });
 4619    _ = editor.update(cx, |editor, window, cx| {
 4620        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4621        editor.insert_blocks(
 4622            [BlockProperties {
 4623                style: BlockStyle::Fixed,
 4624                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4625                height: Some(1),
 4626                render: Arc::new(|_| div().into_any()),
 4627                priority: 0,
 4628                render_in_minimap: true,
 4629            }],
 4630            Some(Autoscroll::fit()),
 4631            cx,
 4632        );
 4633        editor.change_selections(None, window, cx, |s| {
 4634            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4635        });
 4636        editor.move_line_down(&MoveLineDown, window, cx);
 4637    });
 4638}
 4639
 4640#[gpui::test]
 4641async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4642    init_test(cx, |_| {});
 4643
 4644    let mut cx = EditorTestContext::new(cx).await;
 4645    cx.set_state(
 4646        &"
 4647            ˇzero
 4648            one
 4649            two
 4650            three
 4651            four
 4652            five
 4653        "
 4654        .unindent(),
 4655    );
 4656
 4657    // Create a four-line block that replaces three lines of text.
 4658    cx.update_editor(|editor, window, cx| {
 4659        let snapshot = editor.snapshot(window, cx);
 4660        let snapshot = &snapshot.buffer_snapshot;
 4661        let placement = BlockPlacement::Replace(
 4662            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4663        );
 4664        editor.insert_blocks(
 4665            [BlockProperties {
 4666                placement,
 4667                height: Some(4),
 4668                style: BlockStyle::Sticky,
 4669                render: Arc::new(|_| gpui::div().into_any_element()),
 4670                priority: 0,
 4671                render_in_minimap: true,
 4672            }],
 4673            None,
 4674            cx,
 4675        );
 4676    });
 4677
 4678    // Move down so that the cursor touches the block.
 4679    cx.update_editor(|editor, window, cx| {
 4680        editor.move_down(&Default::default(), window, cx);
 4681    });
 4682    cx.assert_editor_state(
 4683        &"
 4684            zero
 4685            «one
 4686            two
 4687            threeˇ»
 4688            four
 4689            five
 4690        "
 4691        .unindent(),
 4692    );
 4693
 4694    // Move down past the block.
 4695    cx.update_editor(|editor, window, cx| {
 4696        editor.move_down(&Default::default(), window, cx);
 4697    });
 4698    cx.assert_editor_state(
 4699        &"
 4700            zero
 4701            one
 4702            two
 4703            three
 4704            ˇfour
 4705            five
 4706        "
 4707        .unindent(),
 4708    );
 4709}
 4710
 4711#[gpui::test]
 4712fn test_transpose(cx: &mut TestAppContext) {
 4713    init_test(cx, |_| {});
 4714
 4715    _ = cx.add_window(|window, cx| {
 4716        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4717        editor.set_style(EditorStyle::default(), window, cx);
 4718        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4719        editor.transpose(&Default::default(), window, cx);
 4720        assert_eq!(editor.text(cx), "bac");
 4721        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4722
 4723        editor.transpose(&Default::default(), window, cx);
 4724        assert_eq!(editor.text(cx), "bca");
 4725        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4726
 4727        editor.transpose(&Default::default(), window, cx);
 4728        assert_eq!(editor.text(cx), "bac");
 4729        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4730
 4731        editor
 4732    });
 4733
 4734    _ = cx.add_window(|window, cx| {
 4735        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4736        editor.set_style(EditorStyle::default(), window, cx);
 4737        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4738        editor.transpose(&Default::default(), window, cx);
 4739        assert_eq!(editor.text(cx), "acb\nde");
 4740        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4741
 4742        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4743        editor.transpose(&Default::default(), window, cx);
 4744        assert_eq!(editor.text(cx), "acbd\ne");
 4745        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4746
 4747        editor.transpose(&Default::default(), window, cx);
 4748        assert_eq!(editor.text(cx), "acbde\n");
 4749        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4750
 4751        editor.transpose(&Default::default(), window, cx);
 4752        assert_eq!(editor.text(cx), "acbd\ne");
 4753        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4754
 4755        editor
 4756    });
 4757
 4758    _ = cx.add_window(|window, cx| {
 4759        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4760        editor.set_style(EditorStyle::default(), window, cx);
 4761        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4762        editor.transpose(&Default::default(), window, cx);
 4763        assert_eq!(editor.text(cx), "bacd\ne");
 4764        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4765
 4766        editor.transpose(&Default::default(), window, cx);
 4767        assert_eq!(editor.text(cx), "bcade\n");
 4768        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4769
 4770        editor.transpose(&Default::default(), window, cx);
 4771        assert_eq!(editor.text(cx), "bcda\ne");
 4772        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4773
 4774        editor.transpose(&Default::default(), window, cx);
 4775        assert_eq!(editor.text(cx), "bcade\n");
 4776        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4777
 4778        editor.transpose(&Default::default(), window, cx);
 4779        assert_eq!(editor.text(cx), "bcaed\n");
 4780        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4781
 4782        editor
 4783    });
 4784
 4785    _ = cx.add_window(|window, cx| {
 4786        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4787        editor.set_style(EditorStyle::default(), window, cx);
 4788        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4789        editor.transpose(&Default::default(), window, cx);
 4790        assert_eq!(editor.text(cx), "🏀🍐✋");
 4791        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4792
 4793        editor.transpose(&Default::default(), window, cx);
 4794        assert_eq!(editor.text(cx), "🏀✋🍐");
 4795        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4796
 4797        editor.transpose(&Default::default(), window, cx);
 4798        assert_eq!(editor.text(cx), "🏀🍐✋");
 4799        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4800
 4801        editor
 4802    });
 4803}
 4804
 4805#[gpui::test]
 4806async fn test_rewrap(cx: &mut TestAppContext) {
 4807    init_test(cx, |settings| {
 4808        settings.languages.extend([
 4809            (
 4810                "Markdown".into(),
 4811                LanguageSettingsContent {
 4812                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4813                    ..Default::default()
 4814                },
 4815            ),
 4816            (
 4817                "Plain Text".into(),
 4818                LanguageSettingsContent {
 4819                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4820                    ..Default::default()
 4821                },
 4822            ),
 4823        ])
 4824    });
 4825
 4826    let mut cx = EditorTestContext::new(cx).await;
 4827
 4828    let language_with_c_comments = Arc::new(Language::new(
 4829        LanguageConfig {
 4830            line_comments: vec!["// ".into()],
 4831            ..LanguageConfig::default()
 4832        },
 4833        None,
 4834    ));
 4835    let language_with_pound_comments = Arc::new(Language::new(
 4836        LanguageConfig {
 4837            line_comments: vec!["# ".into()],
 4838            ..LanguageConfig::default()
 4839        },
 4840        None,
 4841    ));
 4842    let markdown_language = Arc::new(Language::new(
 4843        LanguageConfig {
 4844            name: "Markdown".into(),
 4845            ..LanguageConfig::default()
 4846        },
 4847        None,
 4848    ));
 4849    let language_with_doc_comments = Arc::new(Language::new(
 4850        LanguageConfig {
 4851            line_comments: vec!["// ".into(), "/// ".into()],
 4852            ..LanguageConfig::default()
 4853        },
 4854        Some(tree_sitter_rust::LANGUAGE.into()),
 4855    ));
 4856
 4857    let plaintext_language = Arc::new(Language::new(
 4858        LanguageConfig {
 4859            name: "Plain Text".into(),
 4860            ..LanguageConfig::default()
 4861        },
 4862        None,
 4863    ));
 4864
 4865    assert_rewrap(
 4866        indoc! {"
 4867            // ˇ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.
 4868        "},
 4869        indoc! {"
 4870            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4871            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4872            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4873            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4874            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4875            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4876            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4877            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4878            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4879            // porttitor id. Aliquam id accumsan eros.
 4880        "},
 4881        language_with_c_comments.clone(),
 4882        &mut cx,
 4883    );
 4884
 4885    // Test that rewrapping works inside of a selection
 4886    assert_rewrap(
 4887        indoc! {"
 4888            «// 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.ˇ»
 4889        "},
 4890        indoc! {"
 4891            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4892            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4893            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4894            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4895            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4896            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4897            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4898            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4899            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4900            // porttitor id. Aliquam id accumsan eros.ˇ»
 4901        "},
 4902        language_with_c_comments.clone(),
 4903        &mut cx,
 4904    );
 4905
 4906    // Test that cursors that expand to the same region are collapsed.
 4907    assert_rewrap(
 4908        indoc! {"
 4909            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4910            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4911            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4912            // ˇ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.
 4913        "},
 4914        indoc! {"
 4915            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4916            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4917            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4918            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4919            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4920            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4921            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4922            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4923            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4924            // porttitor id. Aliquam id accumsan eros.
 4925        "},
 4926        language_with_c_comments.clone(),
 4927        &mut cx,
 4928    );
 4929
 4930    // Test that non-contiguous selections are treated separately.
 4931    assert_rewrap(
 4932        indoc! {"
 4933            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4934            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4935            //
 4936            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4937            // ˇ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
 4942            // auctor, eu lacinia sapien scelerisque.
 4943            //
 4944            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 4945            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4946            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 4947            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 4948            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 4949            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 4950            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 4951        "},
 4952        language_with_c_comments.clone(),
 4953        &mut cx,
 4954    );
 4955
 4956    // Test that different comment prefixes are supported.
 4957    assert_rewrap(
 4958        indoc! {"
 4959            # ˇ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.
 4960        "},
 4961        indoc! {"
 4962            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4963            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4964            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4965            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4966            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 4967            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 4968            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 4969            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 4970            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 4971            # accumsan eros.
 4972        "},
 4973        language_with_pound_comments.clone(),
 4974        &mut cx,
 4975    );
 4976
 4977    // Test that rewrapping is ignored outside of comments in most languages.
 4978    assert_rewrap(
 4979        indoc! {"
 4980            /// Adds two numbers.
 4981            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4982            fn add(a: u32, b: u32) -> u32 {
 4983                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ˇ
 4984            }
 4985        "},
 4986        indoc! {"
 4987            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 4988            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4989            fn add(a: u32, b: u32) -> u32 {
 4990                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ˇ
 4991            }
 4992        "},
 4993        language_with_doc_comments.clone(),
 4994        &mut cx,
 4995    );
 4996
 4997    // Test that rewrapping works in Markdown and Plain Text languages.
 4998    assert_rewrap(
 4999        indoc! {"
 5000            # Hello
 5001
 5002            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.
 5003        "},
 5004        indoc! {"
 5005            # Hello
 5006
 5007            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5008            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5009            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5010            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5011            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5012            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5013            Integer sit amet scelerisque nisi.
 5014        "},
 5015        markdown_language,
 5016        &mut cx,
 5017    );
 5018
 5019    assert_rewrap(
 5020        indoc! {"
 5021            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.
 5022        "},
 5023        indoc! {"
 5024            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5025            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5026            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5027            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5028            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5029            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5030            Integer sit amet scelerisque nisi.
 5031        "},
 5032        plaintext_language,
 5033        &mut cx,
 5034    );
 5035
 5036    // Test rewrapping unaligned comments in a selection.
 5037    assert_rewrap(
 5038        indoc! {"
 5039            fn foo() {
 5040                if true {
 5041            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5042            // Praesent semper egestas tellus id dignissim.ˇ»
 5043                    do_something();
 5044                } else {
 5045                    //
 5046                }
 5047            }
 5048        "},
 5049        indoc! {"
 5050            fn foo() {
 5051                if true {
 5052            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5053                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5054                    // egestas tellus id dignissim.ˇ»
 5055                    do_something();
 5056                } else {
 5057                    //
 5058                }
 5059            }
 5060        "},
 5061        language_with_doc_comments.clone(),
 5062        &mut cx,
 5063    );
 5064
 5065    assert_rewrap(
 5066        indoc! {"
 5067            fn foo() {
 5068                if true {
 5069            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5070            // Praesent semper egestas tellus id dignissim.»
 5071                    do_something();
 5072                } else {
 5073                    //
 5074                }
 5075
 5076            }
 5077        "},
 5078        indoc! {"
 5079            fn foo() {
 5080                if true {
 5081            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5082                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5083                    // egestas tellus id dignissim.»
 5084                    do_something();
 5085                } else {
 5086                    //
 5087                }
 5088
 5089            }
 5090        "},
 5091        language_with_doc_comments.clone(),
 5092        &mut cx,
 5093    );
 5094
 5095    #[track_caller]
 5096    fn assert_rewrap(
 5097        unwrapped_text: &str,
 5098        wrapped_text: &str,
 5099        language: Arc<Language>,
 5100        cx: &mut EditorTestContext,
 5101    ) {
 5102        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5103        cx.set_state(unwrapped_text);
 5104        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5105        cx.assert_editor_state(wrapped_text);
 5106    }
 5107}
 5108
 5109#[gpui::test]
 5110async fn test_hard_wrap(cx: &mut TestAppContext) {
 5111    init_test(cx, |_| {});
 5112    let mut cx = EditorTestContext::new(cx).await;
 5113
 5114    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5115    cx.update_editor(|editor, _, cx| {
 5116        editor.set_hard_wrap(Some(14), cx);
 5117    });
 5118
 5119    cx.set_state(indoc!(
 5120        "
 5121        one two three ˇ
 5122        "
 5123    ));
 5124    cx.simulate_input("four");
 5125    cx.run_until_parked();
 5126
 5127    cx.assert_editor_state(indoc!(
 5128        "
 5129        one two three
 5130        fourˇ
 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.assert_editor_state(indoc!(
 5139        "
 5140        one two three
 5141        four
 5142        ˇ
 5143        "
 5144    ));
 5145
 5146    cx.simulate_input("five");
 5147    cx.run_until_parked();
 5148    cx.assert_editor_state(indoc!(
 5149        "
 5150        one two three
 5151        four
 5152        fiveˇ
 5153        "
 5154    ));
 5155
 5156    cx.update_editor(|editor, window, cx| {
 5157        editor.newline(&Default::default(), window, cx);
 5158    });
 5159    cx.run_until_parked();
 5160    cx.simulate_input("# ");
 5161    cx.run_until_parked();
 5162    cx.assert_editor_state(indoc!(
 5163        "
 5164        one two three
 5165        four
 5166        five
 5167        # ˇ
 5168        "
 5169    ));
 5170
 5171    cx.update_editor(|editor, window, cx| {
 5172        editor.newline(&Default::default(), window, cx);
 5173    });
 5174    cx.run_until_parked();
 5175    cx.assert_editor_state(indoc!(
 5176        "
 5177        one two three
 5178        four
 5179        five
 5180        #\x20
 5181 5182        "
 5183    ));
 5184
 5185    cx.simulate_input(" 6");
 5186    cx.run_until_parked();
 5187    cx.assert_editor_state(indoc!(
 5188        "
 5189        one two three
 5190        four
 5191        five
 5192        #
 5193        # 6ˇ
 5194        "
 5195    ));
 5196}
 5197
 5198#[gpui::test]
 5199async fn test_clipboard(cx: &mut TestAppContext) {
 5200    init_test(cx, |_| {});
 5201
 5202    let mut cx = EditorTestContext::new(cx).await;
 5203
 5204    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5205    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5206    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5207
 5208    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5209    cx.set_state("two ˇfour ˇsix ˇ");
 5210    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5211    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5212
 5213    // Paste again but with only two cursors. Since the number of cursors doesn't
 5214    // match the number of slices in the clipboard, the entire clipboard text
 5215    // is pasted at each cursor.
 5216    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5217    cx.update_editor(|e, window, cx| {
 5218        e.handle_input("( ", window, cx);
 5219        e.paste(&Paste, window, cx);
 5220        e.handle_input(") ", window, cx);
 5221    });
 5222    cx.assert_editor_state(
 5223        &([
 5224            "( one✅ ",
 5225            "three ",
 5226            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5227            "three ",
 5228            "five ) ˇ",
 5229        ]
 5230        .join("\n")),
 5231    );
 5232
 5233    // Cut with three selections, one of which is full-line.
 5234    cx.set_state(indoc! {"
 5235        1«2ˇ»3
 5236        4ˇ567
 5237        «8ˇ»9"});
 5238    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5239    cx.assert_editor_state(indoc! {"
 5240        1ˇ3
 5241        ˇ9"});
 5242
 5243    // Paste with three selections, noticing how the copied selection that was full-line
 5244    // gets inserted before the second cursor.
 5245    cx.set_state(indoc! {"
 5246        1ˇ3
 5247 5248        «oˇ»ne"});
 5249    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5250    cx.assert_editor_state(indoc! {"
 5251        12ˇ3
 5252        4567
 5253 5254        8ˇne"});
 5255
 5256    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5257    cx.set_state(indoc! {"
 5258        The quick brown
 5259        fox juˇmps over
 5260        the lazy dog"});
 5261    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5262    assert_eq!(
 5263        cx.read_from_clipboard()
 5264            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5265        Some("fox jumps over\n".to_string())
 5266    );
 5267
 5268    // Paste with three selections, noticing how the copied full-line selection is inserted
 5269    // before the empty selections but replaces the selection that is non-empty.
 5270    cx.set_state(indoc! {"
 5271        Tˇhe quick brown
 5272        «foˇ»x jumps over
 5273        tˇhe lazy dog"});
 5274    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5275    cx.assert_editor_state(indoc! {"
 5276        fox jumps over
 5277        Tˇhe quick brown
 5278        fox jumps over
 5279        ˇx jumps over
 5280        fox jumps over
 5281        tˇhe lazy dog"});
 5282}
 5283
 5284#[gpui::test]
 5285async fn test_copy_trim(cx: &mut TestAppContext) {
 5286    init_test(cx, |_| {});
 5287
 5288    let mut cx = EditorTestContext::new(cx).await;
 5289    cx.set_state(
 5290        r#"            «for selection in selections.iter() {
 5291            let mut start = selection.start;
 5292            let mut end = selection.end;
 5293            let is_entire_line = selection.is_empty();
 5294            if is_entire_line {
 5295                start = Point::new(start.row, 0);ˇ»
 5296                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5297            }
 5298        "#,
 5299    );
 5300    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5301    assert_eq!(
 5302        cx.read_from_clipboard()
 5303            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5304        Some(
 5305            "for selection in selections.iter() {
 5306            let mut start = selection.start;
 5307            let mut end = selection.end;
 5308            let is_entire_line = selection.is_empty();
 5309            if is_entire_line {
 5310                start = Point::new(start.row, 0);"
 5311                .to_string()
 5312        ),
 5313        "Regular copying preserves all indentation selected",
 5314    );
 5315    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5316    assert_eq!(
 5317        cx.read_from_clipboard()
 5318            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5319        Some(
 5320            "for selection in selections.iter() {
 5321let mut start = selection.start;
 5322let mut end = selection.end;
 5323let is_entire_line = selection.is_empty();
 5324if is_entire_line {
 5325    start = Point::new(start.row, 0);"
 5326                .to_string()
 5327        ),
 5328        "Copying with stripping should strip all leading whitespaces"
 5329    );
 5330
 5331    cx.set_state(
 5332        r#"       «     for selection in selections.iter() {
 5333            let mut start = selection.start;
 5334            let mut end = selection.end;
 5335            let is_entire_line = selection.is_empty();
 5336            if is_entire_line {
 5337                start = Point::new(start.row, 0);ˇ»
 5338                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5339            }
 5340        "#,
 5341    );
 5342    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5343    assert_eq!(
 5344        cx.read_from_clipboard()
 5345            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5346        Some(
 5347            "     for selection in selections.iter() {
 5348            let mut start = selection.start;
 5349            let mut end = selection.end;
 5350            let is_entire_line = selection.is_empty();
 5351            if is_entire_line {
 5352                start = Point::new(start.row, 0);"
 5353                .to_string()
 5354        ),
 5355        "Regular copying preserves all indentation selected",
 5356    );
 5357    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5358    assert_eq!(
 5359        cx.read_from_clipboard()
 5360            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5361        Some(
 5362            "for selection in selections.iter() {
 5363let mut start = selection.start;
 5364let mut end = selection.end;
 5365let is_entire_line = selection.is_empty();
 5366if is_entire_line {
 5367    start = Point::new(start.row, 0);"
 5368                .to_string()
 5369        ),
 5370        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5371    );
 5372
 5373    cx.set_state(
 5374        r#"       «ˇ     for selection in selections.iter() {
 5375            let mut start = selection.start;
 5376            let mut end = selection.end;
 5377            let is_entire_line = selection.is_empty();
 5378            if is_entire_line {
 5379                start = Point::new(start.row, 0);»
 5380                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5381            }
 5382        "#,
 5383    );
 5384    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5385    assert_eq!(
 5386        cx.read_from_clipboard()
 5387            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5388        Some(
 5389            "     for selection in selections.iter() {
 5390            let mut start = selection.start;
 5391            let mut end = selection.end;
 5392            let is_entire_line = selection.is_empty();
 5393            if is_entire_line {
 5394                start = Point::new(start.row, 0);"
 5395                .to_string()
 5396        ),
 5397        "Regular copying for reverse selection works the same",
 5398    );
 5399    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5400    assert_eq!(
 5401        cx.read_from_clipboard()
 5402            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5403        Some(
 5404            "for selection in selections.iter() {
 5405let mut start = selection.start;
 5406let mut end = selection.end;
 5407let is_entire_line = selection.is_empty();
 5408if is_entire_line {
 5409    start = Point::new(start.row, 0);"
 5410                .to_string()
 5411        ),
 5412        "Copying with stripping for reverse selection works the same"
 5413    );
 5414
 5415    cx.set_state(
 5416        r#"            for selection «in selections.iter() {
 5417            let mut start = selection.start;
 5418            let mut end = selection.end;
 5419            let is_entire_line = selection.is_empty();
 5420            if is_entire_line {
 5421                start = Point::new(start.row, 0);ˇ»
 5422                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5423            }
 5424        "#,
 5425    );
 5426    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5427    assert_eq!(
 5428        cx.read_from_clipboard()
 5429            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5430        Some(
 5431            "in selections.iter() {
 5432            let mut start = selection.start;
 5433            let mut end = selection.end;
 5434            let is_entire_line = selection.is_empty();
 5435            if is_entire_line {
 5436                start = Point::new(start.row, 0);"
 5437                .to_string()
 5438        ),
 5439        "When selecting past the indent, the copying works as usual",
 5440    );
 5441    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5442    assert_eq!(
 5443        cx.read_from_clipboard()
 5444            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5445        Some(
 5446            "in selections.iter() {
 5447            let mut start = selection.start;
 5448            let mut end = selection.end;
 5449            let is_entire_line = selection.is_empty();
 5450            if is_entire_line {
 5451                start = Point::new(start.row, 0);"
 5452                .to_string()
 5453        ),
 5454        "When selecting past the indent, nothing is trimmed"
 5455    );
 5456
 5457    cx.set_state(
 5458        r#"            «for selection in selections.iter() {
 5459            let mut start = selection.start;
 5460
 5461            let mut end = selection.end;
 5462            let is_entire_line = selection.is_empty();
 5463            if is_entire_line {
 5464                start = Point::new(start.row, 0);
 5465ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5466            }
 5467        "#,
 5468    );
 5469    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5470    assert_eq!(
 5471        cx.read_from_clipboard()
 5472            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5473        Some(
 5474            "for selection in selections.iter() {
 5475let mut start = selection.start;
 5476
 5477let mut end = selection.end;
 5478let is_entire_line = selection.is_empty();
 5479if is_entire_line {
 5480    start = Point::new(start.row, 0);
 5481"
 5482            .to_string()
 5483        ),
 5484        "Copying with stripping should ignore empty lines"
 5485    );
 5486}
 5487
 5488#[gpui::test]
 5489async fn test_paste_multiline(cx: &mut TestAppContext) {
 5490    init_test(cx, |_| {});
 5491
 5492    let mut cx = EditorTestContext::new(cx).await;
 5493    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5494
 5495    // Cut an indented block, without the leading whitespace.
 5496    cx.set_state(indoc! {"
 5497        const a: B = (
 5498            c(),
 5499            «d(
 5500                e,
 5501                f
 5502            )ˇ»
 5503        );
 5504    "});
 5505    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5506    cx.assert_editor_state(indoc! {"
 5507        const a: B = (
 5508            c(),
 5509            ˇ
 5510        );
 5511    "});
 5512
 5513    // Paste it at the same position.
 5514    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5515    cx.assert_editor_state(indoc! {"
 5516        const a: B = (
 5517            c(),
 5518            d(
 5519                e,
 5520                f
 5521 5522        );
 5523    "});
 5524
 5525    // Paste it at a line with a lower indent level.
 5526    cx.set_state(indoc! {"
 5527        ˇ
 5528        const a: B = (
 5529            c(),
 5530        );
 5531    "});
 5532    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5533    cx.assert_editor_state(indoc! {"
 5534        d(
 5535            e,
 5536            f
 5537 5538        const a: B = (
 5539            c(),
 5540        );
 5541    "});
 5542
 5543    // Cut an indented block, with the leading whitespace.
 5544    cx.set_state(indoc! {"
 5545        const a: B = (
 5546            c(),
 5547        «    d(
 5548                e,
 5549                f
 5550            )
 5551        ˇ»);
 5552    "});
 5553    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5554    cx.assert_editor_state(indoc! {"
 5555        const a: B = (
 5556            c(),
 5557        ˇ);
 5558    "});
 5559
 5560    // Paste it at the same position.
 5561    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5562    cx.assert_editor_state(indoc! {"
 5563        const a: B = (
 5564            c(),
 5565            d(
 5566                e,
 5567                f
 5568            )
 5569        ˇ);
 5570    "});
 5571
 5572    // Paste it at a line with a higher indent level.
 5573    cx.set_state(indoc! {"
 5574        const a: B = (
 5575            c(),
 5576            d(
 5577                e,
 5578 5579            )
 5580        );
 5581    "});
 5582    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5583    cx.assert_editor_state(indoc! {"
 5584        const a: B = (
 5585            c(),
 5586            d(
 5587                e,
 5588                f    d(
 5589                    e,
 5590                    f
 5591                )
 5592        ˇ
 5593            )
 5594        );
 5595    "});
 5596
 5597    // Copy an indented block, starting mid-line
 5598    cx.set_state(indoc! {"
 5599        const a: B = (
 5600            c(),
 5601            somethin«g(
 5602                e,
 5603                f
 5604            )ˇ»
 5605        );
 5606    "});
 5607    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5608
 5609    // Paste it on a line with a lower indent level
 5610    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5611    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5612    cx.assert_editor_state(indoc! {"
 5613        const a: B = (
 5614            c(),
 5615            something(
 5616                e,
 5617                f
 5618            )
 5619        );
 5620        g(
 5621            e,
 5622            f
 5623"});
 5624}
 5625
 5626#[gpui::test]
 5627async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5628    init_test(cx, |_| {});
 5629
 5630    cx.write_to_clipboard(ClipboardItem::new_string(
 5631        "    d(\n        e\n    );\n".into(),
 5632    ));
 5633
 5634    let mut cx = EditorTestContext::new(cx).await;
 5635    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5636
 5637    cx.set_state(indoc! {"
 5638        fn a() {
 5639            b();
 5640            if c() {
 5641                ˇ
 5642            }
 5643        }
 5644    "});
 5645
 5646    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5647    cx.assert_editor_state(indoc! {"
 5648        fn a() {
 5649            b();
 5650            if c() {
 5651                d(
 5652                    e
 5653                );
 5654        ˇ
 5655            }
 5656        }
 5657    "});
 5658
 5659    cx.set_state(indoc! {"
 5660        fn a() {
 5661            b();
 5662            ˇ
 5663        }
 5664    "});
 5665
 5666    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5667    cx.assert_editor_state(indoc! {"
 5668        fn a() {
 5669            b();
 5670            d(
 5671                e
 5672            );
 5673        ˇ
 5674        }
 5675    "});
 5676}
 5677
 5678#[gpui::test]
 5679fn test_select_all(cx: &mut TestAppContext) {
 5680    init_test(cx, |_| {});
 5681
 5682    let editor = cx.add_window(|window, cx| {
 5683        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5684        build_editor(buffer, window, cx)
 5685    });
 5686    _ = editor.update(cx, |editor, window, cx| {
 5687        editor.select_all(&SelectAll, window, cx);
 5688        assert_eq!(
 5689            editor.selections.display_ranges(cx),
 5690            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5691        );
 5692    });
 5693}
 5694
 5695#[gpui::test]
 5696fn test_select_line(cx: &mut TestAppContext) {
 5697    init_test(cx, |_| {});
 5698
 5699    let editor = cx.add_window(|window, cx| {
 5700        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5701        build_editor(buffer, window, cx)
 5702    });
 5703    _ = editor.update(cx, |editor, window, cx| {
 5704        editor.change_selections(None, window, cx, |s| {
 5705            s.select_display_ranges([
 5706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5707                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5708                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5709                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5710            ])
 5711        });
 5712        editor.select_line(&SelectLine, window, cx);
 5713        assert_eq!(
 5714            editor.selections.display_ranges(cx),
 5715            vec![
 5716                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5717                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5718            ]
 5719        );
 5720    });
 5721
 5722    _ = editor.update(cx, |editor, window, cx| {
 5723        editor.select_line(&SelectLine, window, cx);
 5724        assert_eq!(
 5725            editor.selections.display_ranges(cx),
 5726            vec![
 5727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5728                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5729            ]
 5730        );
 5731    });
 5732
 5733    _ = editor.update(cx, |editor, window, cx| {
 5734        editor.select_line(&SelectLine, window, cx);
 5735        assert_eq!(
 5736            editor.selections.display_ranges(cx),
 5737            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5738        );
 5739    });
 5740}
 5741
 5742#[gpui::test]
 5743async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5744    init_test(cx, |_| {});
 5745    let mut cx = EditorTestContext::new(cx).await;
 5746
 5747    #[track_caller]
 5748    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5749        cx.set_state(initial_state);
 5750        cx.update_editor(|e, window, cx| {
 5751            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5752        });
 5753        cx.assert_editor_state(expected_state);
 5754    }
 5755
 5756    // Selection starts and ends at the middle of lines, left-to-right
 5757    test(
 5758        &mut cx,
 5759        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5760        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5761    );
 5762    // Same thing, right-to-left
 5763    test(
 5764        &mut cx,
 5765        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5766        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5767    );
 5768
 5769    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5770    test(
 5771        &mut cx,
 5772        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5773        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5774    );
 5775    // Same thing, right-to-left
 5776    test(
 5777        &mut cx,
 5778        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5779        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5780    );
 5781
 5782    // Whole buffer, left-to-right, last line ends with newline
 5783    test(
 5784        &mut cx,
 5785        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5786        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5787    );
 5788    // Same thing, right-to-left
 5789    test(
 5790        &mut cx,
 5791        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5792        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5793    );
 5794
 5795    // Starts at the end of a line, ends at the start of another
 5796    test(
 5797        &mut cx,
 5798        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5799        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5800    );
 5801}
 5802
 5803#[gpui::test]
 5804async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5805    init_test(cx, |_| {});
 5806
 5807    let editor = cx.add_window(|window, cx| {
 5808        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5809        build_editor(buffer, window, cx)
 5810    });
 5811
 5812    // setup
 5813    _ = editor.update(cx, |editor, window, cx| {
 5814        editor.fold_creases(
 5815            vec![
 5816                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5817                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5818                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5819            ],
 5820            true,
 5821            window,
 5822            cx,
 5823        );
 5824        assert_eq!(
 5825            editor.display_text(cx),
 5826            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5827        );
 5828    });
 5829
 5830    _ = editor.update(cx, |editor, window, cx| {
 5831        editor.change_selections(None, window, cx, |s| {
 5832            s.select_display_ranges([
 5833                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5834                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5835                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5836                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5837            ])
 5838        });
 5839        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5840        assert_eq!(
 5841            editor.display_text(cx),
 5842            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5843        );
 5844    });
 5845    EditorTestContext::for_editor(editor, cx)
 5846        .await
 5847        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5848
 5849    _ = editor.update(cx, |editor, window, cx| {
 5850        editor.change_selections(None, window, cx, |s| {
 5851            s.select_display_ranges([
 5852                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5853            ])
 5854        });
 5855        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5856        assert_eq!(
 5857            editor.display_text(cx),
 5858            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5859        );
 5860        assert_eq!(
 5861            editor.selections.display_ranges(cx),
 5862            [
 5863                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5864                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5865                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5866                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5867                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5868                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5869                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5870            ]
 5871        );
 5872    });
 5873    EditorTestContext::for_editor(editor, cx)
 5874        .await
 5875        .assert_editor_state(
 5876            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5877        );
 5878}
 5879
 5880#[gpui::test]
 5881async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5882    init_test(cx, |_| {});
 5883
 5884    let mut cx = EditorTestContext::new(cx).await;
 5885
 5886    cx.set_state(indoc!(
 5887        r#"abc
 5888           defˇghi
 5889
 5890           jk
 5891           nlmo
 5892           "#
 5893    ));
 5894
 5895    cx.update_editor(|editor, window, cx| {
 5896        editor.add_selection_above(&Default::default(), window, cx);
 5897    });
 5898
 5899    cx.assert_editor_state(indoc!(
 5900        r#"abcˇ
 5901           defˇghi
 5902
 5903           jk
 5904           nlmo
 5905           "#
 5906    ));
 5907
 5908    cx.update_editor(|editor, window, cx| {
 5909        editor.add_selection_above(&Default::default(), window, cx);
 5910    });
 5911
 5912    cx.assert_editor_state(indoc!(
 5913        r#"abcˇ
 5914            defˇghi
 5915
 5916            jk
 5917            nlmo
 5918            "#
 5919    ));
 5920
 5921    cx.update_editor(|editor, window, cx| {
 5922        editor.add_selection_below(&Default::default(), window, cx);
 5923    });
 5924
 5925    cx.assert_editor_state(indoc!(
 5926        r#"abc
 5927           defˇghi
 5928
 5929           jk
 5930           nlmo
 5931           "#
 5932    ));
 5933
 5934    cx.update_editor(|editor, window, cx| {
 5935        editor.undo_selection(&Default::default(), window, cx);
 5936    });
 5937
 5938    cx.assert_editor_state(indoc!(
 5939        r#"abcˇ
 5940           defˇghi
 5941
 5942           jk
 5943           nlmo
 5944           "#
 5945    ));
 5946
 5947    cx.update_editor(|editor, window, cx| {
 5948        editor.redo_selection(&Default::default(), window, cx);
 5949    });
 5950
 5951    cx.assert_editor_state(indoc!(
 5952        r#"abc
 5953           defˇghi
 5954
 5955           jk
 5956           nlmo
 5957           "#
 5958    ));
 5959
 5960    cx.update_editor(|editor, window, cx| {
 5961        editor.add_selection_below(&Default::default(), window, cx);
 5962    });
 5963
 5964    cx.assert_editor_state(indoc!(
 5965        r#"abc
 5966           defˇghi
 5967
 5968           jk
 5969           nlmˇo
 5970           "#
 5971    ));
 5972
 5973    cx.update_editor(|editor, window, cx| {
 5974        editor.add_selection_below(&Default::default(), window, cx);
 5975    });
 5976
 5977    cx.assert_editor_state(indoc!(
 5978        r#"abc
 5979           defˇghi
 5980
 5981           jk
 5982           nlmˇo
 5983           "#
 5984    ));
 5985
 5986    // change selections
 5987    cx.set_state(indoc!(
 5988        r#"abc
 5989           def«ˇg»hi
 5990
 5991           jk
 5992           nlmo
 5993           "#
 5994    ));
 5995
 5996    cx.update_editor(|editor, window, cx| {
 5997        editor.add_selection_below(&Default::default(), window, cx);
 5998    });
 5999
 6000    cx.assert_editor_state(indoc!(
 6001        r#"abc
 6002           def«ˇg»hi
 6003
 6004           jk
 6005           nlm«ˇo»
 6006           "#
 6007    ));
 6008
 6009    cx.update_editor(|editor, window, cx| {
 6010        editor.add_selection_below(&Default::default(), window, cx);
 6011    });
 6012
 6013    cx.assert_editor_state(indoc!(
 6014        r#"abc
 6015           def«ˇg»hi
 6016
 6017           jk
 6018           nlm«ˇo»
 6019           "#
 6020    ));
 6021
 6022    cx.update_editor(|editor, window, cx| {
 6023        editor.add_selection_above(&Default::default(), window, cx);
 6024    });
 6025
 6026    cx.assert_editor_state(indoc!(
 6027        r#"abc
 6028           def«ˇg»hi
 6029
 6030           jk
 6031           nlmo
 6032           "#
 6033    ));
 6034
 6035    cx.update_editor(|editor, window, cx| {
 6036        editor.add_selection_above(&Default::default(), window, cx);
 6037    });
 6038
 6039    cx.assert_editor_state(indoc!(
 6040        r#"abc
 6041           def«ˇg»hi
 6042
 6043           jk
 6044           nlmo
 6045           "#
 6046    ));
 6047
 6048    // Change selections again
 6049    cx.set_state(indoc!(
 6050        r#"a«bc
 6051           defgˇ»hi
 6052
 6053           jk
 6054           nlmo
 6055           "#
 6056    ));
 6057
 6058    cx.update_editor(|editor, window, cx| {
 6059        editor.add_selection_below(&Default::default(), window, cx);
 6060    });
 6061
 6062    cx.assert_editor_state(indoc!(
 6063        r#"a«bcˇ»
 6064           d«efgˇ»hi
 6065
 6066           j«kˇ»
 6067           nlmo
 6068           "#
 6069    ));
 6070
 6071    cx.update_editor(|editor, window, cx| {
 6072        editor.add_selection_below(&Default::default(), window, cx);
 6073    });
 6074    cx.assert_editor_state(indoc!(
 6075        r#"a«bcˇ»
 6076           d«efgˇ»hi
 6077
 6078           j«kˇ»
 6079           n«lmoˇ»
 6080           "#
 6081    ));
 6082    cx.update_editor(|editor, window, cx| {
 6083        editor.add_selection_above(&Default::default(), window, cx);
 6084    });
 6085
 6086    cx.assert_editor_state(indoc!(
 6087        r#"a«bcˇ»
 6088           d«efgˇ»hi
 6089
 6090           j«kˇ»
 6091           nlmo
 6092           "#
 6093    ));
 6094
 6095    // Change selections again
 6096    cx.set_state(indoc!(
 6097        r#"abc
 6098           d«ˇefghi
 6099
 6100           jk
 6101           nlm»o
 6102           "#
 6103    ));
 6104
 6105    cx.update_editor(|editor, window, cx| {
 6106        editor.add_selection_above(&Default::default(), window, cx);
 6107    });
 6108
 6109    cx.assert_editor_state(indoc!(
 6110        r#"a«ˇbc»
 6111           d«ˇef»ghi
 6112
 6113           j«ˇk»
 6114           n«ˇlm»o
 6115           "#
 6116    ));
 6117
 6118    cx.update_editor(|editor, window, cx| {
 6119        editor.add_selection_below(&Default::default(), window, cx);
 6120    });
 6121
 6122    cx.assert_editor_state(indoc!(
 6123        r#"abc
 6124           d«ˇef»ghi
 6125
 6126           j«ˇk»
 6127           n«ˇlm»o
 6128           "#
 6129    ));
 6130}
 6131
 6132#[gpui::test]
 6133async fn test_select_next(cx: &mut TestAppContext) {
 6134    init_test(cx, |_| {});
 6135
 6136    let mut cx = EditorTestContext::new(cx).await;
 6137    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6138
 6139    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6140        .unwrap();
 6141    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6142
 6143    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6144        .unwrap();
 6145    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6146
 6147    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6148    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6149
 6150    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6151    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6152
 6153    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6154        .unwrap();
 6155    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6156
 6157    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6158        .unwrap();
 6159    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6160
 6161    // Test selection direction should be preserved
 6162    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6163
 6164    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6165        .unwrap();
 6166    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6167}
 6168
 6169#[gpui::test]
 6170async fn test_select_all_matches(cx: &mut TestAppContext) {
 6171    init_test(cx, |_| {});
 6172
 6173    let mut cx = EditorTestContext::new(cx).await;
 6174
 6175    // Test caret-only selections
 6176    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6177    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6178        .unwrap();
 6179    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6180
 6181    // Test left-to-right selections
 6182    cx.set_state("abc\n«abcˇ»\nabc");
 6183    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6184        .unwrap();
 6185    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6186
 6187    // Test right-to-left selections
 6188    cx.set_state("abc\n«ˇabc»\nabc");
 6189    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6190        .unwrap();
 6191    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6192
 6193    // Test selecting whitespace with caret selection
 6194    cx.set_state("abc\nˇ   abc\nabc");
 6195    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6196        .unwrap();
 6197    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6198
 6199    // Test selecting whitespace with left-to-right selection
 6200    cx.set_state("abc\n«ˇ  »abc\nabc");
 6201    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6202        .unwrap();
 6203    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6204
 6205    // Test no matches with right-to-left selection
 6206    cx.set_state("abc\n«  ˇ»abc\nabc");
 6207    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6208        .unwrap();
 6209    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6210}
 6211
 6212#[gpui::test]
 6213async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6214    init_test(cx, |_| {});
 6215
 6216    let mut cx = EditorTestContext::new(cx).await;
 6217
 6218    let large_body_1 = "\nd".repeat(200);
 6219    let large_body_2 = "\ne".repeat(200);
 6220
 6221    cx.set_state(&format!(
 6222        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6223    ));
 6224    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6225        let scroll_position = editor.scroll_position(cx);
 6226        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6227        scroll_position
 6228    });
 6229
 6230    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6231        .unwrap();
 6232    cx.assert_editor_state(&format!(
 6233        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6234    ));
 6235    let scroll_position_after_selection =
 6236        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6237    assert_eq!(
 6238        initial_scroll_position, scroll_position_after_selection,
 6239        "Scroll position should not change after selecting all matches"
 6240    );
 6241}
 6242
 6243#[gpui::test]
 6244async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6245    init_test(cx, |_| {});
 6246
 6247    let mut cx = EditorLspTestContext::new_rust(
 6248        lsp::ServerCapabilities {
 6249            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6250            ..Default::default()
 6251        },
 6252        cx,
 6253    )
 6254    .await;
 6255
 6256    cx.set_state(indoc! {"
 6257        line 1
 6258        line 2
 6259        linˇe 3
 6260        line 4
 6261        line 5
 6262    "});
 6263
 6264    // Make an edit
 6265    cx.update_editor(|editor, window, cx| {
 6266        editor.handle_input("X", window, cx);
 6267    });
 6268
 6269    // Move cursor to a different position
 6270    cx.update_editor(|editor, window, cx| {
 6271        editor.change_selections(None, window, cx, |s| {
 6272            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6273        });
 6274    });
 6275
 6276    cx.assert_editor_state(indoc! {"
 6277        line 1
 6278        line 2
 6279        linXe 3
 6280        line 4
 6281        liˇne 5
 6282    "});
 6283
 6284    cx.lsp
 6285        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6286            Ok(Some(vec![lsp::TextEdit::new(
 6287                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6288                "PREFIX ".to_string(),
 6289            )]))
 6290        });
 6291
 6292    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6293        .unwrap()
 6294        .await
 6295        .unwrap();
 6296
 6297    cx.assert_editor_state(indoc! {"
 6298        PREFIX line 1
 6299        line 2
 6300        linXe 3
 6301        line 4
 6302        liˇne 5
 6303    "});
 6304
 6305    // Undo formatting
 6306    cx.update_editor(|editor, window, cx| {
 6307        editor.undo(&Default::default(), window, cx);
 6308    });
 6309
 6310    // Verify cursor moved back to position after edit
 6311    cx.assert_editor_state(indoc! {"
 6312        line 1
 6313        line 2
 6314        linXˇe 3
 6315        line 4
 6316        line 5
 6317    "});
 6318}
 6319
 6320#[gpui::test]
 6321async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6322    init_test(cx, |_| {});
 6323
 6324    let mut cx = EditorTestContext::new(cx).await;
 6325    cx.set_state(
 6326        r#"let foo = 2;
 6327lˇet foo = 2;
 6328let fooˇ = 2;
 6329let foo = 2;
 6330let foo = ˇ2;"#,
 6331    );
 6332
 6333    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6334        .unwrap();
 6335    cx.assert_editor_state(
 6336        r#"let foo = 2;
 6337«letˇ» foo = 2;
 6338let «fooˇ» = 2;
 6339let foo = 2;
 6340let foo = «2ˇ»;"#,
 6341    );
 6342
 6343    // noop for multiple selections with different contents
 6344    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6345        .unwrap();
 6346    cx.assert_editor_state(
 6347        r#"let foo = 2;
 6348«letˇ» foo = 2;
 6349let «fooˇ» = 2;
 6350let foo = 2;
 6351let foo = «2ˇ»;"#,
 6352    );
 6353
 6354    // Test last selection direction should be preserved
 6355    cx.set_state(
 6356        r#"let foo = 2;
 6357let foo = 2;
 6358let «fooˇ» = 2;
 6359let «ˇfoo» = 2;
 6360let foo = 2;"#,
 6361    );
 6362
 6363    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6364        .unwrap();
 6365    cx.assert_editor_state(
 6366        r#"let foo = 2;
 6367let foo = 2;
 6368let «fooˇ» = 2;
 6369let «ˇfoo» = 2;
 6370let «ˇfoo» = 2;"#,
 6371    );
 6372}
 6373
 6374#[gpui::test]
 6375async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6376    init_test(cx, |_| {});
 6377
 6378    let mut cx =
 6379        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6380
 6381    cx.assert_editor_state(indoc! {"
 6382        ˇbbb
 6383        ccc
 6384
 6385        bbb
 6386        ccc
 6387        "});
 6388    cx.dispatch_action(SelectPrevious::default());
 6389    cx.assert_editor_state(indoc! {"
 6390                «bbbˇ»
 6391                ccc
 6392
 6393                bbb
 6394                ccc
 6395                "});
 6396    cx.dispatch_action(SelectPrevious::default());
 6397    cx.assert_editor_state(indoc! {"
 6398                «bbbˇ»
 6399                ccc
 6400
 6401                «bbbˇ»
 6402                ccc
 6403                "});
 6404}
 6405
 6406#[gpui::test]
 6407async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6408    init_test(cx, |_| {});
 6409
 6410    let mut cx = EditorTestContext::new(cx).await;
 6411    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6412
 6413    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6414        .unwrap();
 6415    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6416
 6417    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6418        .unwrap();
 6419    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6420
 6421    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6422    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6423
 6424    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6425    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6426
 6427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6428        .unwrap();
 6429    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6430
 6431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6432        .unwrap();
 6433    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6434}
 6435
 6436#[gpui::test]
 6437async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6438    init_test(cx, |_| {});
 6439
 6440    let mut cx = EditorTestContext::new(cx).await;
 6441    cx.set_state("");
 6442
 6443    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6444        .unwrap();
 6445    cx.assert_editor_state("«aˇ»");
 6446    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6447        .unwrap();
 6448    cx.assert_editor_state("«aˇ»");
 6449}
 6450
 6451#[gpui::test]
 6452async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6453    init_test(cx, |_| {});
 6454
 6455    let mut cx = EditorTestContext::new(cx).await;
 6456    cx.set_state(
 6457        r#"let foo = 2;
 6458lˇet foo = 2;
 6459let fooˇ = 2;
 6460let foo = 2;
 6461let foo = ˇ2;"#,
 6462    );
 6463
 6464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6465        .unwrap();
 6466    cx.assert_editor_state(
 6467        r#"let foo = 2;
 6468«letˇ» foo = 2;
 6469let «fooˇ» = 2;
 6470let foo = 2;
 6471let foo = «2ˇ»;"#,
 6472    );
 6473
 6474    // noop for multiple selections with different contents
 6475    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6476        .unwrap();
 6477    cx.assert_editor_state(
 6478        r#"let foo = 2;
 6479«letˇ» foo = 2;
 6480let «fooˇ» = 2;
 6481let foo = 2;
 6482let foo = «2ˇ»;"#,
 6483    );
 6484}
 6485
 6486#[gpui::test]
 6487async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6488    init_test(cx, |_| {});
 6489
 6490    let mut cx = EditorTestContext::new(cx).await;
 6491    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6492
 6493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6494        .unwrap();
 6495    // selection direction is preserved
 6496    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6497
 6498    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6499        .unwrap();
 6500    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6501
 6502    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6503    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6504
 6505    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6506    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6507
 6508    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6509        .unwrap();
 6510    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6511
 6512    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6513        .unwrap();
 6514    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6515}
 6516
 6517#[gpui::test]
 6518async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6519    init_test(cx, |_| {});
 6520
 6521    let language = Arc::new(Language::new(
 6522        LanguageConfig::default(),
 6523        Some(tree_sitter_rust::LANGUAGE.into()),
 6524    ));
 6525
 6526    let text = r#"
 6527        use mod1::mod2::{mod3, mod4};
 6528
 6529        fn fn_1(param1: bool, param2: &str) {
 6530            let var1 = "text";
 6531        }
 6532    "#
 6533    .unindent();
 6534
 6535    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6536    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6537    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6538
 6539    editor
 6540        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6541        .await;
 6542
 6543    editor.update_in(cx, |editor, window, cx| {
 6544        editor.change_selections(None, window, cx, |s| {
 6545            s.select_display_ranges([
 6546                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6547                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6548                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6549            ]);
 6550        });
 6551        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6552    });
 6553    editor.update(cx, |editor, cx| {
 6554        assert_text_with_selections(
 6555            editor,
 6556            indoc! {r#"
 6557                use mod1::mod2::{mod3, «mod4ˇ»};
 6558
 6559                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6560                    let var1 = "«ˇtext»";
 6561                }
 6562            "#},
 6563            cx,
 6564        );
 6565    });
 6566
 6567    editor.update_in(cx, |editor, window, cx| {
 6568        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6569    });
 6570    editor.update(cx, |editor, cx| {
 6571        assert_text_with_selections(
 6572            editor,
 6573            indoc! {r#"
 6574                use mod1::mod2::«{mod3, mod4}ˇ»;
 6575
 6576                «ˇfn fn_1(param1: bool, param2: &str) {
 6577                    let var1 = "text";
 6578 6579            "#},
 6580            cx,
 6581        );
 6582    });
 6583
 6584    editor.update_in(cx, |editor, window, cx| {
 6585        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6586    });
 6587    assert_eq!(
 6588        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6589        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6590    );
 6591
 6592    // Trying to expand the selected syntax node one more time has no effect.
 6593    editor.update_in(cx, |editor, window, cx| {
 6594        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6595    });
 6596    assert_eq!(
 6597        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6598        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6599    );
 6600
 6601    editor.update_in(cx, |editor, window, cx| {
 6602        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6603    });
 6604    editor.update(cx, |editor, cx| {
 6605        assert_text_with_selections(
 6606            editor,
 6607            indoc! {r#"
 6608                use mod1::mod2::«{mod3, mod4}ˇ»;
 6609
 6610                «ˇfn fn_1(param1: bool, param2: &str) {
 6611                    let var1 = "text";
 6612 6613            "#},
 6614            cx,
 6615        );
 6616    });
 6617
 6618    editor.update_in(cx, |editor, window, cx| {
 6619        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6620    });
 6621    editor.update(cx, |editor, cx| {
 6622        assert_text_with_selections(
 6623            editor,
 6624            indoc! {r#"
 6625                use mod1::mod2::{mod3, «mod4ˇ»};
 6626
 6627                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6628                    let var1 = "«ˇtext»";
 6629                }
 6630            "#},
 6631            cx,
 6632        );
 6633    });
 6634
 6635    editor.update_in(cx, |editor, window, cx| {
 6636        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6637    });
 6638    editor.update(cx, |editor, cx| {
 6639        assert_text_with_selections(
 6640            editor,
 6641            indoc! {r#"
 6642                use mod1::mod2::{mod3, mo«ˇ»d4};
 6643
 6644                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6645                    let var1 = "te«ˇ»xt";
 6646                }
 6647            "#},
 6648            cx,
 6649        );
 6650    });
 6651
 6652    // Trying to shrink the selected syntax node one more time has no effect.
 6653    editor.update_in(cx, |editor, window, cx| {
 6654        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6655    });
 6656    editor.update_in(cx, |editor, _, cx| {
 6657        assert_text_with_selections(
 6658            editor,
 6659            indoc! {r#"
 6660                use mod1::mod2::{mod3, mo«ˇ»d4};
 6661
 6662                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6663                    let var1 = "te«ˇ»xt";
 6664                }
 6665            "#},
 6666            cx,
 6667        );
 6668    });
 6669
 6670    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6671    // a fold.
 6672    editor.update_in(cx, |editor, window, cx| {
 6673        editor.fold_creases(
 6674            vec![
 6675                Crease::simple(
 6676                    Point::new(0, 21)..Point::new(0, 24),
 6677                    FoldPlaceholder::test(),
 6678                ),
 6679                Crease::simple(
 6680                    Point::new(3, 20)..Point::new(3, 22),
 6681                    FoldPlaceholder::test(),
 6682                ),
 6683            ],
 6684            true,
 6685            window,
 6686            cx,
 6687        );
 6688        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6689    });
 6690    editor.update(cx, |editor, cx| {
 6691        assert_text_with_selections(
 6692            editor,
 6693            indoc! {r#"
 6694                use mod1::mod2::«{mod3, mod4}ˇ»;
 6695
 6696                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6697                    let var1 = "«ˇtext»";
 6698                }
 6699            "#},
 6700            cx,
 6701        );
 6702    });
 6703}
 6704
 6705#[gpui::test]
 6706async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6707    init_test(cx, |_| {});
 6708
 6709    let language = Arc::new(Language::new(
 6710        LanguageConfig::default(),
 6711        Some(tree_sitter_rust::LANGUAGE.into()),
 6712    ));
 6713
 6714    let text = "let a = 2;";
 6715
 6716    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6717    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6718    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6719
 6720    editor
 6721        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6722        .await;
 6723
 6724    // Test case 1: Cursor at end of word
 6725    editor.update_in(cx, |editor, window, cx| {
 6726        editor.change_selections(None, window, cx, |s| {
 6727            s.select_display_ranges([
 6728                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6729            ]);
 6730        });
 6731    });
 6732    editor.update(cx, |editor, cx| {
 6733        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6734    });
 6735    editor.update_in(cx, |editor, window, cx| {
 6736        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6737    });
 6738    editor.update(cx, |editor, cx| {
 6739        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 6740    });
 6741    editor.update_in(cx, |editor, window, cx| {
 6742        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6743    });
 6744    editor.update(cx, |editor, cx| {
 6745        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6746    });
 6747
 6748    // Test case 2: Cursor at end of statement
 6749    editor.update_in(cx, |editor, window, cx| {
 6750        editor.change_selections(None, window, cx, |s| {
 6751            s.select_display_ranges([
 6752                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 6753            ]);
 6754        });
 6755    });
 6756    editor.update(cx, |editor, cx| {
 6757        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 6758    });
 6759    editor.update_in(cx, |editor, window, cx| {
 6760        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6761    });
 6762    editor.update(cx, |editor, cx| {
 6763        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6764    });
 6765}
 6766
 6767#[gpui::test]
 6768async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6769    init_test(cx, |_| {});
 6770
 6771    let language = Arc::new(Language::new(
 6772        LanguageConfig::default(),
 6773        Some(tree_sitter_rust::LANGUAGE.into()),
 6774    ));
 6775
 6776    let text = r#"
 6777        use mod1::mod2::{mod3, mod4};
 6778
 6779        fn fn_1(param1: bool, param2: &str) {
 6780            let var1 = "hello world";
 6781        }
 6782    "#
 6783    .unindent();
 6784
 6785    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6786    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6787    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6788
 6789    editor
 6790        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6791        .await;
 6792
 6793    // Test 1: Cursor on a letter of a string word
 6794    editor.update_in(cx, |editor, window, cx| {
 6795        editor.change_selections(None, window, cx, |s| {
 6796            s.select_display_ranges([
 6797                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6798            ]);
 6799        });
 6800    });
 6801    editor.update_in(cx, |editor, window, cx| {
 6802        assert_text_with_selections(
 6803            editor,
 6804            indoc! {r#"
 6805                use mod1::mod2::{mod3, mod4};
 6806
 6807                fn fn_1(param1: bool, param2: &str) {
 6808                    let var1 = "hˇello world";
 6809                }
 6810            "#},
 6811            cx,
 6812        );
 6813        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, 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 = "«ˇhello» world";
 6821                }
 6822            "#},
 6823            cx,
 6824        );
 6825    });
 6826
 6827    // Test 2: Partial selection within a word
 6828    editor.update_in(cx, |editor, window, cx| {
 6829        editor.change_selections(None, window, cx, |s| {
 6830            s.select_display_ranges([
 6831                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 6832            ]);
 6833        });
 6834    });
 6835    editor.update_in(cx, |editor, window, cx| {
 6836        assert_text_with_selections(
 6837            editor,
 6838            indoc! {r#"
 6839                use mod1::mod2::{mod3, mod4};
 6840
 6841                fn fn_1(param1: bool, param2: &str) {
 6842                    let var1 = "h«elˇ»lo world";
 6843                }
 6844            "#},
 6845            cx,
 6846        );
 6847        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, 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    });
 6860
 6861    // Test 3: Complete word already selected
 6862    editor.update_in(cx, |editor, window, cx| {
 6863        editor.change_selections(None, window, cx, |s| {
 6864            s.select_display_ranges([
 6865                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 6866            ]);
 6867        });
 6868    });
 6869    editor.update_in(cx, |editor, window, cx| {
 6870        assert_text_with_selections(
 6871            editor,
 6872            indoc! {r#"
 6873                use mod1::mod2::{mod3, mod4};
 6874
 6875                fn fn_1(param1: bool, param2: &str) {
 6876                    let var1 = "«helloˇ» world";
 6877                }
 6878            "#},
 6879            cx,
 6880        );
 6881        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, 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 = "«hello worldˇ»";
 6889                }
 6890            "#},
 6891            cx,
 6892        );
 6893    });
 6894
 6895    // Test 4: Selection spanning across words
 6896    editor.update_in(cx, |editor, window, cx| {
 6897        editor.change_selections(None, window, cx, |s| {
 6898            s.select_display_ranges([
 6899                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 6900            ]);
 6901        });
 6902    });
 6903    editor.update_in(cx, |editor, window, cx| {
 6904        assert_text_with_selections(
 6905            editor,
 6906            indoc! {r#"
 6907                use mod1::mod2::{mod3, mod4};
 6908
 6909                fn fn_1(param1: bool, param2: &str) {
 6910                    let var1 = "hel«lo woˇ»rld";
 6911                }
 6912            "#},
 6913            cx,
 6914        );
 6915        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6916        assert_text_with_selections(
 6917            editor,
 6918            indoc! {r#"
 6919                use mod1::mod2::{mod3, mod4};
 6920
 6921                fn fn_1(param1: bool, param2: &str) {
 6922                    let var1 = "«ˇhello world»";
 6923                }
 6924            "#},
 6925            cx,
 6926        );
 6927    });
 6928
 6929    // Test 5: Expansion beyond string
 6930    editor.update_in(cx, |editor, window, cx| {
 6931        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6932        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6933        assert_text_with_selections(
 6934            editor,
 6935            indoc! {r#"
 6936                use mod1::mod2::{mod3, mod4};
 6937
 6938                fn fn_1(param1: bool, param2: &str) {
 6939                    «ˇlet var1 = "hello world";»
 6940                }
 6941            "#},
 6942            cx,
 6943        );
 6944    });
 6945}
 6946
 6947#[gpui::test]
 6948async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6949    init_test(cx, |_| {});
 6950
 6951    let base_text = r#"
 6952        impl A {
 6953            // this is an uncommitted comment
 6954
 6955            fn b() {
 6956                c();
 6957            }
 6958
 6959            // this is another uncommitted comment
 6960
 6961            fn d() {
 6962                // e
 6963                // f
 6964            }
 6965        }
 6966
 6967        fn g() {
 6968            // h
 6969        }
 6970    "#
 6971    .unindent();
 6972
 6973    let text = r#"
 6974        ˇimpl A {
 6975
 6976            fn b() {
 6977                c();
 6978            }
 6979
 6980            fn d() {
 6981                // e
 6982                // f
 6983            }
 6984        }
 6985
 6986        fn g() {
 6987            // h
 6988        }
 6989    "#
 6990    .unindent();
 6991
 6992    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6993    cx.set_state(&text);
 6994    cx.set_head_text(&base_text);
 6995    cx.update_editor(|editor, window, cx| {
 6996        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6997    });
 6998
 6999    cx.assert_state_with_diff(
 7000        "
 7001        ˇimpl A {
 7002      -     // this is an uncommitted comment
 7003
 7004            fn b() {
 7005                c();
 7006            }
 7007
 7008      -     // this is another uncommitted comment
 7009      -
 7010            fn d() {
 7011                // e
 7012                // f
 7013            }
 7014        }
 7015
 7016        fn g() {
 7017            // h
 7018        }
 7019    "
 7020        .unindent(),
 7021    );
 7022
 7023    let expected_display_text = "
 7024        impl A {
 7025            // this is an uncommitted comment
 7026
 7027            fn b() {
 7028 7029            }
 7030
 7031            // this is another uncommitted comment
 7032
 7033            fn d() {
 7034 7035            }
 7036        }
 7037
 7038        fn g() {
 7039 7040        }
 7041        "
 7042    .unindent();
 7043
 7044    cx.update_editor(|editor, window, cx| {
 7045        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7046        assert_eq!(editor.display_text(cx), expected_display_text);
 7047    });
 7048}
 7049
 7050#[gpui::test]
 7051async fn test_autoindent(cx: &mut TestAppContext) {
 7052    init_test(cx, |_| {});
 7053
 7054    let language = Arc::new(
 7055        Language::new(
 7056            LanguageConfig {
 7057                brackets: BracketPairConfig {
 7058                    pairs: vec![
 7059                        BracketPair {
 7060                            start: "{".to_string(),
 7061                            end: "}".to_string(),
 7062                            close: false,
 7063                            surround: false,
 7064                            newline: true,
 7065                        },
 7066                        BracketPair {
 7067                            start: "(".to_string(),
 7068                            end: ")".to_string(),
 7069                            close: false,
 7070                            surround: false,
 7071                            newline: true,
 7072                        },
 7073                    ],
 7074                    ..Default::default()
 7075                },
 7076                ..Default::default()
 7077            },
 7078            Some(tree_sitter_rust::LANGUAGE.into()),
 7079        )
 7080        .with_indents_query(
 7081            r#"
 7082                (_ "(" ")" @end) @indent
 7083                (_ "{" "}" @end) @indent
 7084            "#,
 7085        )
 7086        .unwrap(),
 7087    );
 7088
 7089    let text = "fn a() {}";
 7090
 7091    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7092    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7093    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7094    editor
 7095        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7096        .await;
 7097
 7098    editor.update_in(cx, |editor, window, cx| {
 7099        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7100        editor.newline(&Newline, window, cx);
 7101        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7102        assert_eq!(
 7103            editor.selections.ranges(cx),
 7104            &[
 7105                Point::new(1, 4)..Point::new(1, 4),
 7106                Point::new(3, 4)..Point::new(3, 4),
 7107                Point::new(5, 0)..Point::new(5, 0)
 7108            ]
 7109        );
 7110    });
 7111}
 7112
 7113#[gpui::test]
 7114async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7115    init_test(cx, |_| {});
 7116
 7117    {
 7118        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7119        cx.set_state(indoc! {"
 7120            impl A {
 7121
 7122                fn b() {}
 7123
 7124            «fn c() {
 7125
 7126            }ˇ»
 7127            }
 7128        "});
 7129
 7130        cx.update_editor(|editor, window, cx| {
 7131            editor.autoindent(&Default::default(), window, cx);
 7132        });
 7133
 7134        cx.assert_editor_state(indoc! {"
 7135            impl A {
 7136
 7137                fn b() {}
 7138
 7139                «fn c() {
 7140
 7141                }ˇ»
 7142            }
 7143        "});
 7144    }
 7145
 7146    {
 7147        let mut cx = EditorTestContext::new_multibuffer(
 7148            cx,
 7149            [indoc! { "
 7150                impl A {
 7151                «
 7152                // a
 7153                fn b(){}
 7154                »
 7155                «
 7156                    }
 7157                    fn c(){}
 7158                »
 7159            "}],
 7160        );
 7161
 7162        let buffer = cx.update_editor(|editor, _, cx| {
 7163            let buffer = editor.buffer().update(cx, |buffer, _| {
 7164                buffer.all_buffers().iter().next().unwrap().clone()
 7165            });
 7166            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7167            buffer
 7168        });
 7169
 7170        cx.run_until_parked();
 7171        cx.update_editor(|editor, window, cx| {
 7172            editor.select_all(&Default::default(), window, cx);
 7173            editor.autoindent(&Default::default(), window, cx)
 7174        });
 7175        cx.run_until_parked();
 7176
 7177        cx.update(|_, cx| {
 7178            assert_eq!(
 7179                buffer.read(cx).text(),
 7180                indoc! { "
 7181                    impl A {
 7182
 7183                        // a
 7184                        fn b(){}
 7185
 7186
 7187                    }
 7188                    fn c(){}
 7189
 7190                " }
 7191            )
 7192        });
 7193    }
 7194}
 7195
 7196#[gpui::test]
 7197async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7198    init_test(cx, |_| {});
 7199
 7200    let mut cx = EditorTestContext::new(cx).await;
 7201
 7202    let language = Arc::new(Language::new(
 7203        LanguageConfig {
 7204            brackets: BracketPairConfig {
 7205                pairs: vec![
 7206                    BracketPair {
 7207                        start: "{".to_string(),
 7208                        end: "}".to_string(),
 7209                        close: true,
 7210                        surround: true,
 7211                        newline: true,
 7212                    },
 7213                    BracketPair {
 7214                        start: "(".to_string(),
 7215                        end: ")".to_string(),
 7216                        close: true,
 7217                        surround: true,
 7218                        newline: true,
 7219                    },
 7220                    BracketPair {
 7221                        start: "/*".to_string(),
 7222                        end: " */".to_string(),
 7223                        close: true,
 7224                        surround: true,
 7225                        newline: true,
 7226                    },
 7227                    BracketPair {
 7228                        start: "[".to_string(),
 7229                        end: "]".to_string(),
 7230                        close: false,
 7231                        surround: false,
 7232                        newline: true,
 7233                    },
 7234                    BracketPair {
 7235                        start: "\"".to_string(),
 7236                        end: "\"".to_string(),
 7237                        close: true,
 7238                        surround: true,
 7239                        newline: false,
 7240                    },
 7241                    BracketPair {
 7242                        start: "<".to_string(),
 7243                        end: ">".to_string(),
 7244                        close: false,
 7245                        surround: true,
 7246                        newline: true,
 7247                    },
 7248                ],
 7249                ..Default::default()
 7250            },
 7251            autoclose_before: "})]".to_string(),
 7252            ..Default::default()
 7253        },
 7254        Some(tree_sitter_rust::LANGUAGE.into()),
 7255    ));
 7256
 7257    cx.language_registry().add(language.clone());
 7258    cx.update_buffer(|buffer, cx| {
 7259        buffer.set_language(Some(language), cx);
 7260    });
 7261
 7262    cx.set_state(
 7263        &r#"
 7264            🏀ˇ
 7265            εˇ
 7266            ❤️ˇ
 7267        "#
 7268        .unindent(),
 7269    );
 7270
 7271    // autoclose multiple nested brackets at multiple cursors
 7272    cx.update_editor(|editor, window, cx| {
 7273        editor.handle_input("{", window, cx);
 7274        editor.handle_input("{", window, cx);
 7275        editor.handle_input("{", window, cx);
 7276    });
 7277    cx.assert_editor_state(
 7278        &"
 7279            🏀{{{ˇ}}}
 7280            ε{{{ˇ}}}
 7281            ❤️{{{ˇ}}}
 7282        "
 7283        .unindent(),
 7284    );
 7285
 7286    // insert a different closing bracket
 7287    cx.update_editor(|editor, window, cx| {
 7288        editor.handle_input(")", window, cx);
 7289    });
 7290    cx.assert_editor_state(
 7291        &"
 7292            🏀{{{)ˇ}}}
 7293            ε{{{)ˇ}}}
 7294            ❤️{{{)ˇ}}}
 7295        "
 7296        .unindent(),
 7297    );
 7298
 7299    // skip over the auto-closed brackets when typing a closing bracket
 7300    cx.update_editor(|editor, window, cx| {
 7301        editor.move_right(&MoveRight, window, cx);
 7302        editor.handle_input("}", window, cx);
 7303        editor.handle_input("}", window, cx);
 7304        editor.handle_input("}", window, cx);
 7305    });
 7306    cx.assert_editor_state(
 7307        &"
 7308            🏀{{{)}}}}ˇ
 7309            ε{{{)}}}}ˇ
 7310            ❤️{{{)}}}}ˇ
 7311        "
 7312        .unindent(),
 7313    );
 7314
 7315    // autoclose multi-character pairs
 7316    cx.set_state(
 7317        &"
 7318            ˇ
 7319            ˇ
 7320        "
 7321        .unindent(),
 7322    );
 7323    cx.update_editor(|editor, window, cx| {
 7324        editor.handle_input("/", window, cx);
 7325        editor.handle_input("*", window, cx);
 7326    });
 7327    cx.assert_editor_state(
 7328        &"
 7329            /*ˇ */
 7330            /*ˇ */
 7331        "
 7332        .unindent(),
 7333    );
 7334
 7335    // one cursor autocloses a multi-character pair, one cursor
 7336    // does not autoclose.
 7337    cx.set_state(
 7338        &"
 7339 7340            ˇ
 7341        "
 7342        .unindent(),
 7343    );
 7344    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7345    cx.assert_editor_state(
 7346        &"
 7347            /*ˇ */
 7348 7349        "
 7350        .unindent(),
 7351    );
 7352
 7353    // Don't autoclose if the next character isn't whitespace and isn't
 7354    // listed in the language's "autoclose_before" section.
 7355    cx.set_state("ˇa b");
 7356    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7357    cx.assert_editor_state("{ˇa b");
 7358
 7359    // Don't autoclose if `close` is false for the bracket pair
 7360    cx.set_state("ˇ");
 7361    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7362    cx.assert_editor_state("");
 7363
 7364    // Surround with brackets if text is selected
 7365    cx.set_state("«aˇ» b");
 7366    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7367    cx.assert_editor_state("{«aˇ»} b");
 7368
 7369    // Autoclose when not immediately after a word character
 7370    cx.set_state("a ˇ");
 7371    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7372    cx.assert_editor_state("a \"ˇ\"");
 7373
 7374    // Autoclose pair where the start and end characters are the same
 7375    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7376    cx.assert_editor_state("a \"\"ˇ");
 7377
 7378    // Don't autoclose when immediately after a word character
 7379    cx.set_state("");
 7380    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7381    cx.assert_editor_state("a\"ˇ");
 7382
 7383    // Do autoclose when after a non-word character
 7384    cx.set_state("");
 7385    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7386    cx.assert_editor_state("{\"ˇ\"");
 7387
 7388    // Non identical pairs autoclose regardless of preceding character
 7389    cx.set_state("");
 7390    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7391    cx.assert_editor_state("a{ˇ}");
 7392
 7393    // Don't autoclose pair if autoclose is disabled
 7394    cx.set_state("ˇ");
 7395    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7396    cx.assert_editor_state("");
 7397
 7398    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7399    cx.set_state("«aˇ» b");
 7400    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7401    cx.assert_editor_state("<«aˇ»> b");
 7402}
 7403
 7404#[gpui::test]
 7405async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7406    init_test(cx, |settings| {
 7407        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7408    });
 7409
 7410    let mut cx = EditorTestContext::new(cx).await;
 7411
 7412    let language = Arc::new(Language::new(
 7413        LanguageConfig {
 7414            brackets: BracketPairConfig {
 7415                pairs: vec![
 7416                    BracketPair {
 7417                        start: "{".to_string(),
 7418                        end: "}".to_string(),
 7419                        close: true,
 7420                        surround: true,
 7421                        newline: true,
 7422                    },
 7423                    BracketPair {
 7424                        start: "(".to_string(),
 7425                        end: ")".to_string(),
 7426                        close: true,
 7427                        surround: true,
 7428                        newline: true,
 7429                    },
 7430                    BracketPair {
 7431                        start: "[".to_string(),
 7432                        end: "]".to_string(),
 7433                        close: false,
 7434                        surround: false,
 7435                        newline: true,
 7436                    },
 7437                ],
 7438                ..Default::default()
 7439            },
 7440            autoclose_before: "})]".to_string(),
 7441            ..Default::default()
 7442        },
 7443        Some(tree_sitter_rust::LANGUAGE.into()),
 7444    ));
 7445
 7446    cx.language_registry().add(language.clone());
 7447    cx.update_buffer(|buffer, cx| {
 7448        buffer.set_language(Some(language), cx);
 7449    });
 7450
 7451    cx.set_state(
 7452        &"
 7453            ˇ
 7454            ˇ
 7455            ˇ
 7456        "
 7457        .unindent(),
 7458    );
 7459
 7460    // ensure only matching closing brackets are skipped over
 7461    cx.update_editor(|editor, window, cx| {
 7462        editor.handle_input("}", window, cx);
 7463        editor.move_left(&MoveLeft, window, cx);
 7464        editor.handle_input(")", window, cx);
 7465        editor.move_left(&MoveLeft, window, cx);
 7466    });
 7467    cx.assert_editor_state(
 7468        &"
 7469            ˇ)}
 7470            ˇ)}
 7471            ˇ)}
 7472        "
 7473        .unindent(),
 7474    );
 7475
 7476    // skip-over closing brackets at multiple cursors
 7477    cx.update_editor(|editor, window, cx| {
 7478        editor.handle_input(")", window, cx);
 7479        editor.handle_input("}", window, cx);
 7480    });
 7481    cx.assert_editor_state(
 7482        &"
 7483            )}ˇ
 7484            )}ˇ
 7485            )}ˇ
 7486        "
 7487        .unindent(),
 7488    );
 7489
 7490    // ignore non-close brackets
 7491    cx.update_editor(|editor, window, cx| {
 7492        editor.handle_input("]", window, cx);
 7493        editor.move_left(&MoveLeft, window, cx);
 7494        editor.handle_input("]", window, cx);
 7495    });
 7496    cx.assert_editor_state(
 7497        &"
 7498            )}]ˇ]
 7499            )}]ˇ]
 7500            )}]ˇ]
 7501        "
 7502        .unindent(),
 7503    );
 7504}
 7505
 7506#[gpui::test]
 7507async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7508    init_test(cx, |_| {});
 7509
 7510    let mut cx = EditorTestContext::new(cx).await;
 7511
 7512    let html_language = Arc::new(
 7513        Language::new(
 7514            LanguageConfig {
 7515                name: "HTML".into(),
 7516                brackets: BracketPairConfig {
 7517                    pairs: vec![
 7518                        BracketPair {
 7519                            start: "<".into(),
 7520                            end: ">".into(),
 7521                            close: true,
 7522                            ..Default::default()
 7523                        },
 7524                        BracketPair {
 7525                            start: "{".into(),
 7526                            end: "}".into(),
 7527                            close: true,
 7528                            ..Default::default()
 7529                        },
 7530                        BracketPair {
 7531                            start: "(".into(),
 7532                            end: ")".into(),
 7533                            close: true,
 7534                            ..Default::default()
 7535                        },
 7536                    ],
 7537                    ..Default::default()
 7538                },
 7539                autoclose_before: "})]>".into(),
 7540                ..Default::default()
 7541            },
 7542            Some(tree_sitter_html::LANGUAGE.into()),
 7543        )
 7544        .with_injection_query(
 7545            r#"
 7546            (script_element
 7547                (raw_text) @injection.content
 7548                (#set! injection.language "javascript"))
 7549            "#,
 7550        )
 7551        .unwrap(),
 7552    );
 7553
 7554    let javascript_language = Arc::new(Language::new(
 7555        LanguageConfig {
 7556            name: "JavaScript".into(),
 7557            brackets: BracketPairConfig {
 7558                pairs: vec![
 7559                    BracketPair {
 7560                        start: "/*".into(),
 7561                        end: " */".into(),
 7562                        close: true,
 7563                        ..Default::default()
 7564                    },
 7565                    BracketPair {
 7566                        start: "{".into(),
 7567                        end: "}".into(),
 7568                        close: true,
 7569                        ..Default::default()
 7570                    },
 7571                    BracketPair {
 7572                        start: "(".into(),
 7573                        end: ")".into(),
 7574                        close: true,
 7575                        ..Default::default()
 7576                    },
 7577                ],
 7578                ..Default::default()
 7579            },
 7580            autoclose_before: "})]>".into(),
 7581            ..Default::default()
 7582        },
 7583        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7584    ));
 7585
 7586    cx.language_registry().add(html_language.clone());
 7587    cx.language_registry().add(javascript_language.clone());
 7588
 7589    cx.update_buffer(|buffer, cx| {
 7590        buffer.set_language(Some(html_language), cx);
 7591    });
 7592
 7593    cx.set_state(
 7594        &r#"
 7595            <body>ˇ
 7596                <script>
 7597                    var x = 1;ˇ
 7598                </script>
 7599            </body>ˇ
 7600        "#
 7601        .unindent(),
 7602    );
 7603
 7604    // Precondition: different languages are active at different locations.
 7605    cx.update_editor(|editor, window, cx| {
 7606        let snapshot = editor.snapshot(window, cx);
 7607        let cursors = editor.selections.ranges::<usize>(cx);
 7608        let languages = cursors
 7609            .iter()
 7610            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7611            .collect::<Vec<_>>();
 7612        assert_eq!(
 7613            languages,
 7614            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7615        );
 7616    });
 7617
 7618    // Angle brackets autoclose in HTML, but not JavaScript.
 7619    cx.update_editor(|editor, window, cx| {
 7620        editor.handle_input("<", window, cx);
 7621        editor.handle_input("a", window, cx);
 7622    });
 7623    cx.assert_editor_state(
 7624        &r#"
 7625            <body><aˇ>
 7626                <script>
 7627                    var x = 1;<aˇ
 7628                </script>
 7629            </body><aˇ>
 7630        "#
 7631        .unindent(),
 7632    );
 7633
 7634    // Curly braces and parens autoclose in both HTML and JavaScript.
 7635    cx.update_editor(|editor, window, cx| {
 7636        editor.handle_input(" b=", window, cx);
 7637        editor.handle_input("{", window, cx);
 7638        editor.handle_input("c", window, cx);
 7639        editor.handle_input("(", window, cx);
 7640    });
 7641    cx.assert_editor_state(
 7642        &r#"
 7643            <body><a b={c(ˇ)}>
 7644                <script>
 7645                    var x = 1;<a b={c(ˇ)}
 7646                </script>
 7647            </body><a b={c(ˇ)}>
 7648        "#
 7649        .unindent(),
 7650    );
 7651
 7652    // Brackets that were already autoclosed are skipped.
 7653    cx.update_editor(|editor, window, cx| {
 7654        editor.handle_input(")", window, cx);
 7655        editor.handle_input("d", window, cx);
 7656        editor.handle_input("}", window, cx);
 7657    });
 7658    cx.assert_editor_state(
 7659        &r#"
 7660            <body><a b={c()d}ˇ>
 7661                <script>
 7662                    var x = 1;<a b={c()d}ˇ
 7663                </script>
 7664            </body><a b={c()d}ˇ>
 7665        "#
 7666        .unindent(),
 7667    );
 7668    cx.update_editor(|editor, window, cx| {
 7669        editor.handle_input(">", window, cx);
 7670    });
 7671    cx.assert_editor_state(
 7672        &r#"
 7673            <body><a b={c()d}>ˇ
 7674                <script>
 7675                    var x = 1;<a b={c()d}>ˇ
 7676                </script>
 7677            </body><a b={c()d}>ˇ
 7678        "#
 7679        .unindent(),
 7680    );
 7681
 7682    // Reset
 7683    cx.set_state(
 7684        &r#"
 7685            <body>ˇ
 7686                <script>
 7687                    var x = 1;ˇ
 7688                </script>
 7689            </body>ˇ
 7690        "#
 7691        .unindent(),
 7692    );
 7693
 7694    cx.update_editor(|editor, window, cx| {
 7695        editor.handle_input("<", window, cx);
 7696    });
 7697    cx.assert_editor_state(
 7698        &r#"
 7699            <body><ˇ>
 7700                <script>
 7701                    var x = 1;<ˇ
 7702                </script>
 7703            </body><ˇ>
 7704        "#
 7705        .unindent(),
 7706    );
 7707
 7708    // When backspacing, the closing angle brackets are removed.
 7709    cx.update_editor(|editor, window, cx| {
 7710        editor.backspace(&Backspace, window, cx);
 7711    });
 7712    cx.assert_editor_state(
 7713        &r#"
 7714            <body>ˇ
 7715                <script>
 7716                    var x = 1;ˇ
 7717                </script>
 7718            </body>ˇ
 7719        "#
 7720        .unindent(),
 7721    );
 7722
 7723    // Block comments autoclose in JavaScript, but not HTML.
 7724    cx.update_editor(|editor, window, cx| {
 7725        editor.handle_input("/", window, cx);
 7726        editor.handle_input("*", window, cx);
 7727    });
 7728    cx.assert_editor_state(
 7729        &r#"
 7730            <body>/*ˇ
 7731                <script>
 7732                    var x = 1;/*ˇ */
 7733                </script>
 7734            </body>/*ˇ
 7735        "#
 7736        .unindent(),
 7737    );
 7738}
 7739
 7740#[gpui::test]
 7741async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7742    init_test(cx, |_| {});
 7743
 7744    let mut cx = EditorTestContext::new(cx).await;
 7745
 7746    let rust_language = Arc::new(
 7747        Language::new(
 7748            LanguageConfig {
 7749                name: "Rust".into(),
 7750                brackets: serde_json::from_value(json!([
 7751                    { "start": "{", "end": "}", "close": true, "newline": true },
 7752                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7753                ]))
 7754                .unwrap(),
 7755                autoclose_before: "})]>".into(),
 7756                ..Default::default()
 7757            },
 7758            Some(tree_sitter_rust::LANGUAGE.into()),
 7759        )
 7760        .with_override_query("(string_literal) @string")
 7761        .unwrap(),
 7762    );
 7763
 7764    cx.language_registry().add(rust_language.clone());
 7765    cx.update_buffer(|buffer, cx| {
 7766        buffer.set_language(Some(rust_language), cx);
 7767    });
 7768
 7769    cx.set_state(
 7770        &r#"
 7771            let x = ˇ
 7772        "#
 7773        .unindent(),
 7774    );
 7775
 7776    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7777    cx.update_editor(|editor, window, cx| {
 7778        editor.handle_input("\"", window, cx);
 7779    });
 7780    cx.assert_editor_state(
 7781        &r#"
 7782            let x = "ˇ"
 7783        "#
 7784        .unindent(),
 7785    );
 7786
 7787    // Inserting another quotation mark. The cursor moves across the existing
 7788    // automatically-inserted quotation mark.
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.handle_input("\"", window, cx);
 7791    });
 7792    cx.assert_editor_state(
 7793        &r#"
 7794            let x = ""ˇ
 7795        "#
 7796        .unindent(),
 7797    );
 7798
 7799    // Reset
 7800    cx.set_state(
 7801        &r#"
 7802            let x = ˇ
 7803        "#
 7804        .unindent(),
 7805    );
 7806
 7807    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7808    cx.update_editor(|editor, window, cx| {
 7809        editor.handle_input("\"", window, cx);
 7810        editor.handle_input(" ", window, cx);
 7811        editor.move_left(&Default::default(), window, cx);
 7812        editor.handle_input("\\", window, cx);
 7813        editor.handle_input("\"", window, cx);
 7814    });
 7815    cx.assert_editor_state(
 7816        &r#"
 7817            let x = "\"ˇ "
 7818        "#
 7819        .unindent(),
 7820    );
 7821
 7822    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7823    // mark. Nothing is inserted.
 7824    cx.update_editor(|editor, window, cx| {
 7825        editor.move_right(&Default::default(), window, cx);
 7826        editor.handle_input("\"", window, cx);
 7827    });
 7828    cx.assert_editor_state(
 7829        &r#"
 7830            let x = "\" "ˇ
 7831        "#
 7832        .unindent(),
 7833    );
 7834}
 7835
 7836#[gpui::test]
 7837async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7838    init_test(cx, |_| {});
 7839
 7840    let language = Arc::new(Language::new(
 7841        LanguageConfig {
 7842            brackets: BracketPairConfig {
 7843                pairs: vec![
 7844                    BracketPair {
 7845                        start: "{".to_string(),
 7846                        end: "}".to_string(),
 7847                        close: true,
 7848                        surround: true,
 7849                        newline: true,
 7850                    },
 7851                    BracketPair {
 7852                        start: "/* ".to_string(),
 7853                        end: "*/".to_string(),
 7854                        close: true,
 7855                        surround: true,
 7856                        ..Default::default()
 7857                    },
 7858                ],
 7859                ..Default::default()
 7860            },
 7861            ..Default::default()
 7862        },
 7863        Some(tree_sitter_rust::LANGUAGE.into()),
 7864    ));
 7865
 7866    let text = r#"
 7867        a
 7868        b
 7869        c
 7870    "#
 7871    .unindent();
 7872
 7873    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7874    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7875    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7876    editor
 7877        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7878        .await;
 7879
 7880    editor.update_in(cx, |editor, window, cx| {
 7881        editor.change_selections(None, window, cx, |s| {
 7882            s.select_display_ranges([
 7883                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7884                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7885                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7886            ])
 7887        });
 7888
 7889        editor.handle_input("{", window, cx);
 7890        editor.handle_input("{", window, cx);
 7891        editor.handle_input("{", window, cx);
 7892        assert_eq!(
 7893            editor.text(cx),
 7894            "
 7895                {{{a}}}
 7896                {{{b}}}
 7897                {{{c}}}
 7898            "
 7899            .unindent()
 7900        );
 7901        assert_eq!(
 7902            editor.selections.display_ranges(cx),
 7903            [
 7904                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7905                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7906                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7907            ]
 7908        );
 7909
 7910        editor.undo(&Undo, window, cx);
 7911        editor.undo(&Undo, window, cx);
 7912        editor.undo(&Undo, window, cx);
 7913        assert_eq!(
 7914            editor.text(cx),
 7915            "
 7916                a
 7917                b
 7918                c
 7919            "
 7920            .unindent()
 7921        );
 7922        assert_eq!(
 7923            editor.selections.display_ranges(cx),
 7924            [
 7925                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7926                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7927                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7928            ]
 7929        );
 7930
 7931        // Ensure inserting the first character of a multi-byte bracket pair
 7932        // doesn't surround the selections with the bracket.
 7933        editor.handle_input("/", window, cx);
 7934        assert_eq!(
 7935            editor.text(cx),
 7936            "
 7937                /
 7938                /
 7939                /
 7940            "
 7941            .unindent()
 7942        );
 7943        assert_eq!(
 7944            editor.selections.display_ranges(cx),
 7945            [
 7946                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7947                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7948                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7949            ]
 7950        );
 7951
 7952        editor.undo(&Undo, window, cx);
 7953        assert_eq!(
 7954            editor.text(cx),
 7955            "
 7956                a
 7957                b
 7958                c
 7959            "
 7960            .unindent()
 7961        );
 7962        assert_eq!(
 7963            editor.selections.display_ranges(cx),
 7964            [
 7965                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7966                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7967                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7968            ]
 7969        );
 7970
 7971        // Ensure inserting the last character of a multi-byte bracket pair
 7972        // doesn't surround the selections with the bracket.
 7973        editor.handle_input("*", window, cx);
 7974        assert_eq!(
 7975            editor.text(cx),
 7976            "
 7977                *
 7978                *
 7979                *
 7980            "
 7981            .unindent()
 7982        );
 7983        assert_eq!(
 7984            editor.selections.display_ranges(cx),
 7985            [
 7986                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7987                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7988                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7989            ]
 7990        );
 7991    });
 7992}
 7993
 7994#[gpui::test]
 7995async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7996    init_test(cx, |_| {});
 7997
 7998    let language = Arc::new(Language::new(
 7999        LanguageConfig {
 8000            brackets: BracketPairConfig {
 8001                pairs: vec![BracketPair {
 8002                    start: "{".to_string(),
 8003                    end: "}".to_string(),
 8004                    close: true,
 8005                    surround: true,
 8006                    newline: true,
 8007                }],
 8008                ..Default::default()
 8009            },
 8010            autoclose_before: "}".to_string(),
 8011            ..Default::default()
 8012        },
 8013        Some(tree_sitter_rust::LANGUAGE.into()),
 8014    ));
 8015
 8016    let text = r#"
 8017        a
 8018        b
 8019        c
 8020    "#
 8021    .unindent();
 8022
 8023    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8024    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8025    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8026    editor
 8027        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8028        .await;
 8029
 8030    editor.update_in(cx, |editor, window, cx| {
 8031        editor.change_selections(None, window, cx, |s| {
 8032            s.select_ranges([
 8033                Point::new(0, 1)..Point::new(0, 1),
 8034                Point::new(1, 1)..Point::new(1, 1),
 8035                Point::new(2, 1)..Point::new(2, 1),
 8036            ])
 8037        });
 8038
 8039        editor.handle_input("{", window, cx);
 8040        editor.handle_input("{", window, cx);
 8041        editor.handle_input("_", window, cx);
 8042        assert_eq!(
 8043            editor.text(cx),
 8044            "
 8045                a{{_}}
 8046                b{{_}}
 8047                c{{_}}
 8048            "
 8049            .unindent()
 8050        );
 8051        assert_eq!(
 8052            editor.selections.ranges::<Point>(cx),
 8053            [
 8054                Point::new(0, 4)..Point::new(0, 4),
 8055                Point::new(1, 4)..Point::new(1, 4),
 8056                Point::new(2, 4)..Point::new(2, 4)
 8057            ]
 8058        );
 8059
 8060        editor.backspace(&Default::default(), window, cx);
 8061        editor.backspace(&Default::default(), window, cx);
 8062        assert_eq!(
 8063            editor.text(cx),
 8064            "
 8065                a{}
 8066                b{}
 8067                c{}
 8068            "
 8069            .unindent()
 8070        );
 8071        assert_eq!(
 8072            editor.selections.ranges::<Point>(cx),
 8073            [
 8074                Point::new(0, 2)..Point::new(0, 2),
 8075                Point::new(1, 2)..Point::new(1, 2),
 8076                Point::new(2, 2)..Point::new(2, 2)
 8077            ]
 8078        );
 8079
 8080        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8081        assert_eq!(
 8082            editor.text(cx),
 8083            "
 8084                a
 8085                b
 8086                c
 8087            "
 8088            .unindent()
 8089        );
 8090        assert_eq!(
 8091            editor.selections.ranges::<Point>(cx),
 8092            [
 8093                Point::new(0, 1)..Point::new(0, 1),
 8094                Point::new(1, 1)..Point::new(1, 1),
 8095                Point::new(2, 1)..Point::new(2, 1)
 8096            ]
 8097        );
 8098    });
 8099}
 8100
 8101#[gpui::test]
 8102async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8103    init_test(cx, |settings| {
 8104        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8105    });
 8106
 8107    let mut cx = EditorTestContext::new(cx).await;
 8108
 8109    let language = Arc::new(Language::new(
 8110        LanguageConfig {
 8111            brackets: BracketPairConfig {
 8112                pairs: vec![
 8113                    BracketPair {
 8114                        start: "{".to_string(),
 8115                        end: "}".to_string(),
 8116                        close: true,
 8117                        surround: true,
 8118                        newline: true,
 8119                    },
 8120                    BracketPair {
 8121                        start: "(".to_string(),
 8122                        end: ")".to_string(),
 8123                        close: true,
 8124                        surround: true,
 8125                        newline: true,
 8126                    },
 8127                    BracketPair {
 8128                        start: "[".to_string(),
 8129                        end: "]".to_string(),
 8130                        close: false,
 8131                        surround: true,
 8132                        newline: true,
 8133                    },
 8134                ],
 8135                ..Default::default()
 8136            },
 8137            autoclose_before: "})]".to_string(),
 8138            ..Default::default()
 8139        },
 8140        Some(tree_sitter_rust::LANGUAGE.into()),
 8141    ));
 8142
 8143    cx.language_registry().add(language.clone());
 8144    cx.update_buffer(|buffer, cx| {
 8145        buffer.set_language(Some(language), cx);
 8146    });
 8147
 8148    cx.set_state(
 8149        &"
 8150            {(ˇ)}
 8151            [[ˇ]]
 8152            {(ˇ)}
 8153        "
 8154        .unindent(),
 8155    );
 8156
 8157    cx.update_editor(|editor, window, cx| {
 8158        editor.backspace(&Default::default(), window, cx);
 8159        editor.backspace(&Default::default(), window, cx);
 8160    });
 8161
 8162    cx.assert_editor_state(
 8163        &"
 8164            ˇ
 8165            ˇ]]
 8166            ˇ
 8167        "
 8168        .unindent(),
 8169    );
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.handle_input("{", window, cx);
 8173        editor.handle_input("{", window, cx);
 8174        editor.move_right(&MoveRight, window, cx);
 8175        editor.move_right(&MoveRight, window, cx);
 8176        editor.move_left(&MoveLeft, window, cx);
 8177        editor.move_left(&MoveLeft, window, cx);
 8178        editor.backspace(&Default::default(), window, cx);
 8179    });
 8180
 8181    cx.assert_editor_state(
 8182        &"
 8183            {ˇ}
 8184            {ˇ}]]
 8185            {ˇ}
 8186        "
 8187        .unindent(),
 8188    );
 8189
 8190    cx.update_editor(|editor, window, cx| {
 8191        editor.backspace(&Default::default(), window, cx);
 8192    });
 8193
 8194    cx.assert_editor_state(
 8195        &"
 8196            ˇ
 8197            ˇ]]
 8198            ˇ
 8199        "
 8200        .unindent(),
 8201    );
 8202}
 8203
 8204#[gpui::test]
 8205async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8206    init_test(cx, |_| {});
 8207
 8208    let language = Arc::new(Language::new(
 8209        LanguageConfig::default(),
 8210        Some(tree_sitter_rust::LANGUAGE.into()),
 8211    ));
 8212
 8213    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8214    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8215    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8216    editor
 8217        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8218        .await;
 8219
 8220    editor.update_in(cx, |editor, window, cx| {
 8221        editor.set_auto_replace_emoji_shortcode(true);
 8222
 8223        editor.handle_input("Hello ", window, cx);
 8224        editor.handle_input(":wave", window, cx);
 8225        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8226
 8227        editor.handle_input(":", window, cx);
 8228        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8229
 8230        editor.handle_input(" :smile", window, cx);
 8231        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8232
 8233        editor.handle_input(":", window, cx);
 8234        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8235
 8236        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8237        editor.handle_input(":wave", window, cx);
 8238        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8239
 8240        editor.handle_input(":", window, cx);
 8241        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8242
 8243        editor.handle_input(":1", window, cx);
 8244        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8245
 8246        editor.handle_input(":", window, cx);
 8247        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8248
 8249        // Ensure shortcode does not get replaced when it is part of a word
 8250        editor.handle_input(" Test:wave", window, cx);
 8251        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8252
 8253        editor.handle_input(":", window, cx);
 8254        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8255
 8256        editor.set_auto_replace_emoji_shortcode(false);
 8257
 8258        // Ensure shortcode does not get replaced when auto replace is off
 8259        editor.handle_input(" :wave", window, cx);
 8260        assert_eq!(
 8261            editor.text(cx),
 8262            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8263        );
 8264
 8265        editor.handle_input(":", window, cx);
 8266        assert_eq!(
 8267            editor.text(cx),
 8268            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8269        );
 8270    });
 8271}
 8272
 8273#[gpui::test]
 8274async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8275    init_test(cx, |_| {});
 8276
 8277    let (text, insertion_ranges) = marked_text_ranges(
 8278        indoc! {"
 8279            ˇ
 8280        "},
 8281        false,
 8282    );
 8283
 8284    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8285    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8286
 8287    _ = editor.update_in(cx, |editor, window, cx| {
 8288        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8289
 8290        editor
 8291            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8292            .unwrap();
 8293
 8294        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8295            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8296            assert_eq!(editor.text(cx), expected_text);
 8297            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8298        }
 8299
 8300        assert(
 8301            editor,
 8302            cx,
 8303            indoc! {"
 8304            type «» =•
 8305            "},
 8306        );
 8307
 8308        assert!(editor.context_menu_visible(), "There should be a matches");
 8309    });
 8310}
 8311
 8312#[gpui::test]
 8313async fn test_snippets(cx: &mut TestAppContext) {
 8314    init_test(cx, |_| {});
 8315
 8316    let (text, insertion_ranges) = marked_text_ranges(
 8317        indoc! {"
 8318            a.ˇ b
 8319            a.ˇ b
 8320            a.ˇ b
 8321        "},
 8322        false,
 8323    );
 8324
 8325    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8326    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8327
 8328    editor.update_in(cx, |editor, window, cx| {
 8329        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8330
 8331        editor
 8332            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8333            .unwrap();
 8334
 8335        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8336            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8337            assert_eq!(editor.text(cx), expected_text);
 8338            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8339        }
 8340
 8341        assert(
 8342            editor,
 8343            cx,
 8344            indoc! {"
 8345                a.f(«one», two, «three») b
 8346                a.f(«one», two, «three») b
 8347                a.f(«one», two, «three») b
 8348            "},
 8349        );
 8350
 8351        // Can't move earlier than the first tab stop
 8352        assert!(!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
 8374        editor.move_to_prev_snippet_tabstop(window, cx);
 8375        assert(
 8376            editor,
 8377            cx,
 8378            indoc! {"
 8379                a.f(«one», two, «three») b
 8380                a.f(«one», two, «three») b
 8381                a.f(«one», two, «three») b
 8382            "},
 8383        );
 8384
 8385        assert!(editor.move_to_next_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        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8396        assert(
 8397            editor,
 8398            cx,
 8399            indoc! {"
 8400                a.f(one, two, three)ˇ b
 8401                a.f(one, two, three)ˇ b
 8402                a.f(one, two, three)ˇ b
 8403            "},
 8404        );
 8405
 8406        // As soon as the last tab stop is reached, snippet state is gone
 8407        editor.move_to_prev_snippet_tabstop(window, cx);
 8408        assert(
 8409            editor,
 8410            cx,
 8411            indoc! {"
 8412                a.f(one, two, three)ˇ b
 8413                a.f(one, two, three)ˇ b
 8414                a.f(one, two, three)ˇ b
 8415            "},
 8416        );
 8417    });
 8418}
 8419
 8420#[gpui::test]
 8421async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8422    init_test(cx, |_| {});
 8423
 8424    let fs = FakeFs::new(cx.executor());
 8425    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8426
 8427    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8428
 8429    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8430    language_registry.add(rust_lang());
 8431    let mut fake_servers = language_registry.register_fake_lsp(
 8432        "Rust",
 8433        FakeLspAdapter {
 8434            capabilities: lsp::ServerCapabilities {
 8435                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8436                ..Default::default()
 8437            },
 8438            ..Default::default()
 8439        },
 8440    );
 8441
 8442    let buffer = project
 8443        .update(cx, |project, cx| {
 8444            project.open_local_buffer(path!("/file.rs"), cx)
 8445        })
 8446        .await
 8447        .unwrap();
 8448
 8449    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8450    let (editor, cx) = cx.add_window_view(|window, cx| {
 8451        build_editor_with_project(project.clone(), buffer, window, cx)
 8452    });
 8453    editor.update_in(cx, |editor, window, cx| {
 8454        editor.set_text("one\ntwo\nthree\n", window, cx)
 8455    });
 8456    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8457
 8458    cx.executor().start_waiting();
 8459    let fake_server = fake_servers.next().await.unwrap();
 8460
 8461    {
 8462        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8463            move |params, _| async move {
 8464                assert_eq!(
 8465                    params.text_document.uri,
 8466                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8467                );
 8468                assert_eq!(params.options.tab_size, 4);
 8469                Ok(Some(vec![lsp::TextEdit::new(
 8470                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8471                    ", ".to_string(),
 8472                )]))
 8473            },
 8474        );
 8475        let save = editor
 8476            .update_in(cx, |editor, window, cx| {
 8477                editor.save(true, project.clone(), window, cx)
 8478            })
 8479            .unwrap();
 8480        cx.executor().start_waiting();
 8481        save.await;
 8482
 8483        assert_eq!(
 8484            editor.update(cx, |editor, cx| editor.text(cx)),
 8485            "one, two\nthree\n"
 8486        );
 8487        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8488    }
 8489
 8490    {
 8491        editor.update_in(cx, |editor, window, cx| {
 8492            editor.set_text("one\ntwo\nthree\n", window, cx)
 8493        });
 8494        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8495
 8496        // Ensure we can still save even if formatting hangs.
 8497        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8498            move |params, _| async move {
 8499                assert_eq!(
 8500                    params.text_document.uri,
 8501                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8502                );
 8503                futures::future::pending::<()>().await;
 8504                unreachable!()
 8505            },
 8506        );
 8507        let save = editor
 8508            .update_in(cx, |editor, window, cx| {
 8509                editor.save(true, project.clone(), window, cx)
 8510            })
 8511            .unwrap();
 8512        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8513        cx.executor().start_waiting();
 8514        save.await;
 8515        assert_eq!(
 8516            editor.update(cx, |editor, cx| editor.text(cx)),
 8517            "one\ntwo\nthree\n"
 8518        );
 8519    }
 8520
 8521    // For non-dirty buffer, no formatting request should be sent
 8522    {
 8523        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8524
 8525        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8526            panic!("Should not be invoked on non-dirty buffer");
 8527        });
 8528        let save = editor
 8529            .update_in(cx, |editor, window, cx| {
 8530                editor.save(true, project.clone(), window, cx)
 8531            })
 8532            .unwrap();
 8533        cx.executor().start_waiting();
 8534        save.await;
 8535    }
 8536
 8537    // Set rust language override and assert overridden tabsize is sent to language server
 8538    update_test_language_settings(cx, |settings| {
 8539        settings.languages.insert(
 8540            "Rust".into(),
 8541            LanguageSettingsContent {
 8542                tab_size: NonZeroU32::new(8),
 8543                ..Default::default()
 8544            },
 8545        );
 8546    });
 8547
 8548    {
 8549        editor.update_in(cx, |editor, window, cx| {
 8550            editor.set_text("somehting_new\n", window, cx)
 8551        });
 8552        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8553        let _formatting_request_signal = fake_server
 8554            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8555                assert_eq!(
 8556                    params.text_document.uri,
 8557                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8558                );
 8559                assert_eq!(params.options.tab_size, 8);
 8560                Ok(Some(vec![]))
 8561            });
 8562        let save = editor
 8563            .update_in(cx, |editor, window, cx| {
 8564                editor.save(true, project.clone(), window, cx)
 8565            })
 8566            .unwrap();
 8567        cx.executor().start_waiting();
 8568        save.await;
 8569    }
 8570}
 8571
 8572#[gpui::test]
 8573async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8574    init_test(cx, |_| {});
 8575
 8576    let cols = 4;
 8577    let rows = 10;
 8578    let sample_text_1 = sample_text(rows, cols, 'a');
 8579    assert_eq!(
 8580        sample_text_1,
 8581        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8582    );
 8583    let sample_text_2 = sample_text(rows, cols, 'l');
 8584    assert_eq!(
 8585        sample_text_2,
 8586        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8587    );
 8588    let sample_text_3 = sample_text(rows, cols, 'v');
 8589    assert_eq!(
 8590        sample_text_3,
 8591        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8592    );
 8593
 8594    let fs = FakeFs::new(cx.executor());
 8595    fs.insert_tree(
 8596        path!("/a"),
 8597        json!({
 8598            "main.rs": sample_text_1,
 8599            "other.rs": sample_text_2,
 8600            "lib.rs": sample_text_3,
 8601        }),
 8602    )
 8603    .await;
 8604
 8605    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8606    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8607    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8608
 8609    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8610    language_registry.add(rust_lang());
 8611    let mut fake_servers = language_registry.register_fake_lsp(
 8612        "Rust",
 8613        FakeLspAdapter {
 8614            capabilities: lsp::ServerCapabilities {
 8615                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8616                ..Default::default()
 8617            },
 8618            ..Default::default()
 8619        },
 8620    );
 8621
 8622    let worktree = project.update(cx, |project, cx| {
 8623        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8624        assert_eq!(worktrees.len(), 1);
 8625        worktrees.pop().unwrap()
 8626    });
 8627    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8628
 8629    let buffer_1 = project
 8630        .update(cx, |project, cx| {
 8631            project.open_buffer((worktree_id, "main.rs"), cx)
 8632        })
 8633        .await
 8634        .unwrap();
 8635    let buffer_2 = project
 8636        .update(cx, |project, cx| {
 8637            project.open_buffer((worktree_id, "other.rs"), cx)
 8638        })
 8639        .await
 8640        .unwrap();
 8641    let buffer_3 = project
 8642        .update(cx, |project, cx| {
 8643            project.open_buffer((worktree_id, "lib.rs"), cx)
 8644        })
 8645        .await
 8646        .unwrap();
 8647
 8648    let multi_buffer = cx.new(|cx| {
 8649        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8650        multi_buffer.push_excerpts(
 8651            buffer_1.clone(),
 8652            [
 8653                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8654                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8655                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8656            ],
 8657            cx,
 8658        );
 8659        multi_buffer.push_excerpts(
 8660            buffer_2.clone(),
 8661            [
 8662                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8663                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8664                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8665            ],
 8666            cx,
 8667        );
 8668        multi_buffer.push_excerpts(
 8669            buffer_3.clone(),
 8670            [
 8671                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8672                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8673                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8674            ],
 8675            cx,
 8676        );
 8677        multi_buffer
 8678    });
 8679    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8680        Editor::new(
 8681            EditorMode::full(),
 8682            multi_buffer,
 8683            Some(project.clone()),
 8684            window,
 8685            cx,
 8686        )
 8687    });
 8688
 8689    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8690        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8691            s.select_ranges(Some(1..2))
 8692        });
 8693        editor.insert("|one|two|three|", window, cx);
 8694    });
 8695    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8696    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8697        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8698            s.select_ranges(Some(60..70))
 8699        });
 8700        editor.insert("|four|five|six|", window, cx);
 8701    });
 8702    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8703
 8704    // First two buffers should be edited, but not the third one.
 8705    assert_eq!(
 8706        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8707        "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}",
 8708    );
 8709    buffer_1.update(cx, |buffer, _| {
 8710        assert!(buffer.is_dirty());
 8711        assert_eq!(
 8712            buffer.text(),
 8713            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8714        )
 8715    });
 8716    buffer_2.update(cx, |buffer, _| {
 8717        assert!(buffer.is_dirty());
 8718        assert_eq!(
 8719            buffer.text(),
 8720            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8721        )
 8722    });
 8723    buffer_3.update(cx, |buffer, _| {
 8724        assert!(!buffer.is_dirty());
 8725        assert_eq!(buffer.text(), sample_text_3,)
 8726    });
 8727    cx.executor().run_until_parked();
 8728
 8729    cx.executor().start_waiting();
 8730    let save = multi_buffer_editor
 8731        .update_in(cx, |editor, window, cx| {
 8732            editor.save(true, project.clone(), window, cx)
 8733        })
 8734        .unwrap();
 8735
 8736    let fake_server = fake_servers.next().await.unwrap();
 8737    fake_server
 8738        .server
 8739        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8740            Ok(Some(vec![lsp::TextEdit::new(
 8741                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8742                format!("[{} formatted]", params.text_document.uri),
 8743            )]))
 8744        })
 8745        .detach();
 8746    save.await;
 8747
 8748    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8749    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8750    assert_eq!(
 8751        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8752        uri!(
 8753            "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}"
 8754        ),
 8755    );
 8756    buffer_1.update(cx, |buffer, _| {
 8757        assert!(!buffer.is_dirty());
 8758        assert_eq!(
 8759            buffer.text(),
 8760            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8761        )
 8762    });
 8763    buffer_2.update(cx, |buffer, _| {
 8764        assert!(!buffer.is_dirty());
 8765        assert_eq!(
 8766            buffer.text(),
 8767            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8768        )
 8769    });
 8770    buffer_3.update(cx, |buffer, _| {
 8771        assert!(!buffer.is_dirty());
 8772        assert_eq!(buffer.text(), sample_text_3,)
 8773    });
 8774}
 8775
 8776#[gpui::test]
 8777async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8778    init_test(cx, |_| {});
 8779
 8780    let fs = FakeFs::new(cx.executor());
 8781    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8782
 8783    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8784
 8785    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8786    language_registry.add(rust_lang());
 8787    let mut fake_servers = language_registry.register_fake_lsp(
 8788        "Rust",
 8789        FakeLspAdapter {
 8790            capabilities: lsp::ServerCapabilities {
 8791                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8792                ..Default::default()
 8793            },
 8794            ..Default::default()
 8795        },
 8796    );
 8797
 8798    let buffer = project
 8799        .update(cx, |project, cx| {
 8800            project.open_local_buffer(path!("/file.rs"), cx)
 8801        })
 8802        .await
 8803        .unwrap();
 8804
 8805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8806    let (editor, cx) = cx.add_window_view(|window, cx| {
 8807        build_editor_with_project(project.clone(), buffer, window, cx)
 8808    });
 8809    editor.update_in(cx, |editor, window, cx| {
 8810        editor.set_text("one\ntwo\nthree\n", window, cx)
 8811    });
 8812    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8813
 8814    cx.executor().start_waiting();
 8815    let fake_server = fake_servers.next().await.unwrap();
 8816
 8817    let save = editor
 8818        .update_in(cx, |editor, window, cx| {
 8819            editor.save(true, project.clone(), window, cx)
 8820        })
 8821        .unwrap();
 8822    fake_server
 8823        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8824            assert_eq!(
 8825                params.text_document.uri,
 8826                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8827            );
 8828            assert_eq!(params.options.tab_size, 4);
 8829            Ok(Some(vec![lsp::TextEdit::new(
 8830                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8831                ", ".to_string(),
 8832            )]))
 8833        })
 8834        .next()
 8835        .await;
 8836    cx.executor().start_waiting();
 8837    save.await;
 8838    assert_eq!(
 8839        editor.update(cx, |editor, cx| editor.text(cx)),
 8840        "one, two\nthree\n"
 8841    );
 8842    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8843
 8844    editor.update_in(cx, |editor, window, cx| {
 8845        editor.set_text("one\ntwo\nthree\n", window, cx)
 8846    });
 8847    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8848
 8849    // Ensure we can still save even if formatting hangs.
 8850    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 8851        move |params, _| async move {
 8852            assert_eq!(
 8853                params.text_document.uri,
 8854                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8855            );
 8856            futures::future::pending::<()>().await;
 8857            unreachable!()
 8858        },
 8859    );
 8860    let save = editor
 8861        .update_in(cx, |editor, window, cx| {
 8862            editor.save(true, project.clone(), window, cx)
 8863        })
 8864        .unwrap();
 8865    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8866    cx.executor().start_waiting();
 8867    save.await;
 8868    assert_eq!(
 8869        editor.update(cx, |editor, cx| editor.text(cx)),
 8870        "one\ntwo\nthree\n"
 8871    );
 8872    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8873
 8874    // For non-dirty buffer, no formatting request should be sent
 8875    let save = editor
 8876        .update_in(cx, |editor, window, cx| {
 8877            editor.save(true, project.clone(), window, cx)
 8878        })
 8879        .unwrap();
 8880    let _pending_format_request = fake_server
 8881        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8882            panic!("Should not be invoked on non-dirty buffer");
 8883        })
 8884        .next();
 8885    cx.executor().start_waiting();
 8886    save.await;
 8887
 8888    // Set Rust language override and assert overridden tabsize is sent to language server
 8889    update_test_language_settings(cx, |settings| {
 8890        settings.languages.insert(
 8891            "Rust".into(),
 8892            LanguageSettingsContent {
 8893                tab_size: NonZeroU32::new(8),
 8894                ..Default::default()
 8895            },
 8896        );
 8897    });
 8898
 8899    editor.update_in(cx, |editor, window, cx| {
 8900        editor.set_text("somehting_new\n", window, cx)
 8901    });
 8902    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8903    let save = editor
 8904        .update_in(cx, |editor, window, cx| {
 8905            editor.save(true, project.clone(), window, cx)
 8906        })
 8907        .unwrap();
 8908    fake_server
 8909        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8910            assert_eq!(
 8911                params.text_document.uri,
 8912                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8913            );
 8914            assert_eq!(params.options.tab_size, 8);
 8915            Ok(Some(vec![]))
 8916        })
 8917        .next()
 8918        .await;
 8919    cx.executor().start_waiting();
 8920    save.await;
 8921}
 8922
 8923#[gpui::test]
 8924async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8925    init_test(cx, |settings| {
 8926        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8927            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8928        ))
 8929    });
 8930
 8931    let fs = FakeFs::new(cx.executor());
 8932    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8933
 8934    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8935
 8936    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8937    language_registry.add(Arc::new(Language::new(
 8938        LanguageConfig {
 8939            name: "Rust".into(),
 8940            matcher: LanguageMatcher {
 8941                path_suffixes: vec!["rs".to_string()],
 8942                ..Default::default()
 8943            },
 8944            ..LanguageConfig::default()
 8945        },
 8946        Some(tree_sitter_rust::LANGUAGE.into()),
 8947    )));
 8948    update_test_language_settings(cx, |settings| {
 8949        // Enable Prettier formatting for the same buffer, and ensure
 8950        // LSP is called instead of Prettier.
 8951        settings.defaults.prettier = Some(PrettierSettings {
 8952            allowed: true,
 8953            ..PrettierSettings::default()
 8954        });
 8955    });
 8956    let mut fake_servers = language_registry.register_fake_lsp(
 8957        "Rust",
 8958        FakeLspAdapter {
 8959            capabilities: lsp::ServerCapabilities {
 8960                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8961                ..Default::default()
 8962            },
 8963            ..Default::default()
 8964        },
 8965    );
 8966
 8967    let buffer = project
 8968        .update(cx, |project, cx| {
 8969            project.open_local_buffer(path!("/file.rs"), cx)
 8970        })
 8971        .await
 8972        .unwrap();
 8973
 8974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8975    let (editor, cx) = cx.add_window_view(|window, cx| {
 8976        build_editor_with_project(project.clone(), buffer, window, cx)
 8977    });
 8978    editor.update_in(cx, |editor, window, cx| {
 8979        editor.set_text("one\ntwo\nthree\n", window, cx)
 8980    });
 8981
 8982    cx.executor().start_waiting();
 8983    let fake_server = fake_servers.next().await.unwrap();
 8984
 8985    let format = editor
 8986        .update_in(cx, |editor, window, cx| {
 8987            editor.perform_format(
 8988                project.clone(),
 8989                FormatTrigger::Manual,
 8990                FormatTarget::Buffers,
 8991                window,
 8992                cx,
 8993            )
 8994        })
 8995        .unwrap();
 8996    fake_server
 8997        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8998            assert_eq!(
 8999                params.text_document.uri,
 9000                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9001            );
 9002            assert_eq!(params.options.tab_size, 4);
 9003            Ok(Some(vec![lsp::TextEdit::new(
 9004                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9005                ", ".to_string(),
 9006            )]))
 9007        })
 9008        .next()
 9009        .await;
 9010    cx.executor().start_waiting();
 9011    format.await;
 9012    assert_eq!(
 9013        editor.update(cx, |editor, cx| editor.text(cx)),
 9014        "one, two\nthree\n"
 9015    );
 9016
 9017    editor.update_in(cx, |editor, window, cx| {
 9018        editor.set_text("one\ntwo\nthree\n", window, cx)
 9019    });
 9020    // Ensure we don't lock if formatting hangs.
 9021    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9022        move |params, _| async move {
 9023            assert_eq!(
 9024                params.text_document.uri,
 9025                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9026            );
 9027            futures::future::pending::<()>().await;
 9028            unreachable!()
 9029        },
 9030    );
 9031    let format = editor
 9032        .update_in(cx, |editor, window, cx| {
 9033            editor.perform_format(
 9034                project,
 9035                FormatTrigger::Manual,
 9036                FormatTarget::Buffers,
 9037                window,
 9038                cx,
 9039            )
 9040        })
 9041        .unwrap();
 9042    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9043    cx.executor().start_waiting();
 9044    format.await;
 9045    assert_eq!(
 9046        editor.update(cx, |editor, cx| editor.text(cx)),
 9047        "one\ntwo\nthree\n"
 9048    );
 9049}
 9050
 9051#[gpui::test]
 9052async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9053    init_test(cx, |settings| {
 9054        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9055        settings.defaults.formatter =
 9056            Some(language_settings::SelectedFormatter::List(FormatterList(
 9057                vec![
 9058                    Formatter::LanguageServer { name: None },
 9059                    Formatter::CodeActions(
 9060                        [
 9061                            ("code-action-1".into(), true),
 9062                            ("code-action-2".into(), true),
 9063                        ]
 9064                        .into_iter()
 9065                        .collect(),
 9066                    ),
 9067                ]
 9068                .into(),
 9069            )))
 9070    });
 9071
 9072    let fs = FakeFs::new(cx.executor());
 9073    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9074        .await;
 9075
 9076    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9077    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9078    language_registry.add(rust_lang());
 9079
 9080    let mut fake_servers = language_registry.register_fake_lsp(
 9081        "Rust",
 9082        FakeLspAdapter {
 9083            capabilities: lsp::ServerCapabilities {
 9084                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9085                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9086                    commands: vec!["the-command-for-code-action-1".into()],
 9087                    ..Default::default()
 9088                }),
 9089                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9090                ..Default::default()
 9091            },
 9092            ..Default::default()
 9093        },
 9094    );
 9095
 9096    let buffer = project
 9097        .update(cx, |project, cx| {
 9098            project.open_local_buffer(path!("/file.rs"), cx)
 9099        })
 9100        .await
 9101        .unwrap();
 9102
 9103    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9104    let (editor, cx) = cx.add_window_view(|window, cx| {
 9105        build_editor_with_project(project.clone(), buffer, window, cx)
 9106    });
 9107
 9108    cx.executor().start_waiting();
 9109
 9110    let fake_server = fake_servers.next().await.unwrap();
 9111    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9112        move |_params, _| async move {
 9113            Ok(Some(vec![lsp::TextEdit::new(
 9114                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9115                "applied-formatting\n".to_string(),
 9116            )]))
 9117        },
 9118    );
 9119    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9120        move |params, _| async move {
 9121            assert_eq!(
 9122                params.context.only,
 9123                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9124            );
 9125            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9126            Ok(Some(vec![
 9127                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9128                    kind: Some("code-action-1".into()),
 9129                    edit: Some(lsp::WorkspaceEdit::new(
 9130                        [(
 9131                            uri.clone(),
 9132                            vec![lsp::TextEdit::new(
 9133                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9134                                "applied-code-action-1-edit\n".to_string(),
 9135                            )],
 9136                        )]
 9137                        .into_iter()
 9138                        .collect(),
 9139                    )),
 9140                    command: Some(lsp::Command {
 9141                        command: "the-command-for-code-action-1".into(),
 9142                        ..Default::default()
 9143                    }),
 9144                    ..Default::default()
 9145                }),
 9146                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9147                    kind: Some("code-action-2".into()),
 9148                    edit: Some(lsp::WorkspaceEdit::new(
 9149                        [(
 9150                            uri.clone(),
 9151                            vec![lsp::TextEdit::new(
 9152                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9153                                "applied-code-action-2-edit\n".to_string(),
 9154                            )],
 9155                        )]
 9156                        .into_iter()
 9157                        .collect(),
 9158                    )),
 9159                    ..Default::default()
 9160                }),
 9161            ]))
 9162        },
 9163    );
 9164
 9165    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9166        move |params, _| async move { Ok(params) }
 9167    });
 9168
 9169    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9170    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9171        let fake = fake_server.clone();
 9172        let lock = command_lock.clone();
 9173        move |params, _| {
 9174            assert_eq!(params.command, "the-command-for-code-action-1");
 9175            let fake = fake.clone();
 9176            let lock = lock.clone();
 9177            async move {
 9178                lock.lock().await;
 9179                fake.server
 9180                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9181                        label: None,
 9182                        edit: lsp::WorkspaceEdit {
 9183                            changes: Some(
 9184                                [(
 9185                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9186                                    vec![lsp::TextEdit {
 9187                                        range: lsp::Range::new(
 9188                                            lsp::Position::new(0, 0),
 9189                                            lsp::Position::new(0, 0),
 9190                                        ),
 9191                                        new_text: "applied-code-action-1-command\n".into(),
 9192                                    }],
 9193                                )]
 9194                                .into_iter()
 9195                                .collect(),
 9196                            ),
 9197                            ..Default::default()
 9198                        },
 9199                    })
 9200                    .await
 9201                    .into_response()
 9202                    .unwrap();
 9203                Ok(Some(json!(null)))
 9204            }
 9205        }
 9206    });
 9207
 9208    cx.executor().start_waiting();
 9209    editor
 9210        .update_in(cx, |editor, window, cx| {
 9211            editor.perform_format(
 9212                project.clone(),
 9213                FormatTrigger::Manual,
 9214                FormatTarget::Buffers,
 9215                window,
 9216                cx,
 9217            )
 9218        })
 9219        .unwrap()
 9220        .await;
 9221    editor.update(cx, |editor, cx| {
 9222        assert_eq!(
 9223            editor.text(cx),
 9224            r#"
 9225                applied-code-action-2-edit
 9226                applied-code-action-1-command
 9227                applied-code-action-1-edit
 9228                applied-formatting
 9229                one
 9230                two
 9231                three
 9232            "#
 9233            .unindent()
 9234        );
 9235    });
 9236
 9237    editor.update_in(cx, |editor, window, cx| {
 9238        editor.undo(&Default::default(), window, cx);
 9239        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9240    });
 9241
 9242    // Perform a manual edit while waiting for an LSP command
 9243    // that's being run as part of a formatting code action.
 9244    let lock_guard = command_lock.lock().await;
 9245    let format = editor
 9246        .update_in(cx, |editor, window, cx| {
 9247            editor.perform_format(
 9248                project.clone(),
 9249                FormatTrigger::Manual,
 9250                FormatTarget::Buffers,
 9251                window,
 9252                cx,
 9253            )
 9254        })
 9255        .unwrap();
 9256    cx.run_until_parked();
 9257    editor.update(cx, |editor, cx| {
 9258        assert_eq!(
 9259            editor.text(cx),
 9260            r#"
 9261                applied-code-action-1-edit
 9262                applied-formatting
 9263                one
 9264                two
 9265                three
 9266            "#
 9267            .unindent()
 9268        );
 9269
 9270        editor.buffer.update(cx, |buffer, cx| {
 9271            let ix = buffer.len(cx);
 9272            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9273        });
 9274    });
 9275
 9276    // Allow the LSP command to proceed. Because the buffer was edited,
 9277    // the second code action will not be run.
 9278    drop(lock_guard);
 9279    format.await;
 9280    editor.update_in(cx, |editor, window, cx| {
 9281        assert_eq!(
 9282            editor.text(cx),
 9283            r#"
 9284                applied-code-action-1-command
 9285                applied-code-action-1-edit
 9286                applied-formatting
 9287                one
 9288                two
 9289                three
 9290                edited
 9291            "#
 9292            .unindent()
 9293        );
 9294
 9295        // The manual edit is undone first, because it is the last thing the user did
 9296        // (even though the command completed afterwards).
 9297        editor.undo(&Default::default(), window, cx);
 9298        assert_eq!(
 9299            editor.text(cx),
 9300            r#"
 9301                applied-code-action-1-command
 9302                applied-code-action-1-edit
 9303                applied-formatting
 9304                one
 9305                two
 9306                three
 9307            "#
 9308            .unindent()
 9309        );
 9310
 9311        // All the formatting (including the command, which completed after the manual edit)
 9312        // is undone together.
 9313        editor.undo(&Default::default(), window, cx);
 9314        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9315    });
 9316}
 9317
 9318#[gpui::test]
 9319async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9320    init_test(cx, |settings| {
 9321        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9322            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9323        ))
 9324    });
 9325
 9326    let fs = FakeFs::new(cx.executor());
 9327    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9328
 9329    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9330
 9331    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9332    language_registry.add(Arc::new(Language::new(
 9333        LanguageConfig {
 9334            name: "TypeScript".into(),
 9335            matcher: LanguageMatcher {
 9336                path_suffixes: vec!["ts".to_string()],
 9337                ..Default::default()
 9338            },
 9339            ..LanguageConfig::default()
 9340        },
 9341        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9342    )));
 9343    update_test_language_settings(cx, |settings| {
 9344        settings.defaults.prettier = Some(PrettierSettings {
 9345            allowed: true,
 9346            ..PrettierSettings::default()
 9347        });
 9348    });
 9349    let mut fake_servers = language_registry.register_fake_lsp(
 9350        "TypeScript",
 9351        FakeLspAdapter {
 9352            capabilities: lsp::ServerCapabilities {
 9353                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9354                ..Default::default()
 9355            },
 9356            ..Default::default()
 9357        },
 9358    );
 9359
 9360    let buffer = project
 9361        .update(cx, |project, cx| {
 9362            project.open_local_buffer(path!("/file.ts"), cx)
 9363        })
 9364        .await
 9365        .unwrap();
 9366
 9367    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9368    let (editor, cx) = cx.add_window_view(|window, cx| {
 9369        build_editor_with_project(project.clone(), buffer, window, cx)
 9370    });
 9371    editor.update_in(cx, |editor, window, cx| {
 9372        editor.set_text(
 9373            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9374            window,
 9375            cx,
 9376        )
 9377    });
 9378
 9379    cx.executor().start_waiting();
 9380    let fake_server = fake_servers.next().await.unwrap();
 9381
 9382    let format = editor
 9383        .update_in(cx, |editor, window, cx| {
 9384            editor.perform_code_action_kind(
 9385                project.clone(),
 9386                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9387                window,
 9388                cx,
 9389            )
 9390        })
 9391        .unwrap();
 9392    fake_server
 9393        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9394            assert_eq!(
 9395                params.text_document.uri,
 9396                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9397            );
 9398            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9399                lsp::CodeAction {
 9400                    title: "Organize Imports".to_string(),
 9401                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9402                    edit: Some(lsp::WorkspaceEdit {
 9403                        changes: Some(
 9404                            [(
 9405                                params.text_document.uri.clone(),
 9406                                vec![lsp::TextEdit::new(
 9407                                    lsp::Range::new(
 9408                                        lsp::Position::new(1, 0),
 9409                                        lsp::Position::new(2, 0),
 9410                                    ),
 9411                                    "".to_string(),
 9412                                )],
 9413                            )]
 9414                            .into_iter()
 9415                            .collect(),
 9416                        ),
 9417                        ..Default::default()
 9418                    }),
 9419                    ..Default::default()
 9420                },
 9421            )]))
 9422        })
 9423        .next()
 9424        .await;
 9425    cx.executor().start_waiting();
 9426    format.await;
 9427    assert_eq!(
 9428        editor.update(cx, |editor, cx| editor.text(cx)),
 9429        "import { a } from 'module';\n\nconst x = a;\n"
 9430    );
 9431
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.set_text(
 9434            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9435            window,
 9436            cx,
 9437        )
 9438    });
 9439    // Ensure we don't lock if code action hangs.
 9440    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9441        move |params, _| async move {
 9442            assert_eq!(
 9443                params.text_document.uri,
 9444                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9445            );
 9446            futures::future::pending::<()>().await;
 9447            unreachable!()
 9448        },
 9449    );
 9450    let format = editor
 9451        .update_in(cx, |editor, window, cx| {
 9452            editor.perform_code_action_kind(
 9453                project,
 9454                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9455                window,
 9456                cx,
 9457            )
 9458        })
 9459        .unwrap();
 9460    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9461    cx.executor().start_waiting();
 9462    format.await;
 9463    assert_eq!(
 9464        editor.update(cx, |editor, cx| editor.text(cx)),
 9465        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9466    );
 9467}
 9468
 9469#[gpui::test]
 9470async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9471    init_test(cx, |_| {});
 9472
 9473    let mut cx = EditorLspTestContext::new_rust(
 9474        lsp::ServerCapabilities {
 9475            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9476            ..Default::default()
 9477        },
 9478        cx,
 9479    )
 9480    .await;
 9481
 9482    cx.set_state(indoc! {"
 9483        one.twoˇ
 9484    "});
 9485
 9486    // The format request takes a long time. When it completes, it inserts
 9487    // a newline and an indent before the `.`
 9488    cx.lsp
 9489        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9490            let executor = cx.background_executor().clone();
 9491            async move {
 9492                executor.timer(Duration::from_millis(100)).await;
 9493                Ok(Some(vec![lsp::TextEdit {
 9494                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9495                    new_text: "\n    ".into(),
 9496                }]))
 9497            }
 9498        });
 9499
 9500    // Submit a format request.
 9501    let format_1 = cx
 9502        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9503        .unwrap();
 9504    cx.executor().run_until_parked();
 9505
 9506    // Submit a second format request.
 9507    let format_2 = cx
 9508        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9509        .unwrap();
 9510    cx.executor().run_until_parked();
 9511
 9512    // Wait for both format requests to complete
 9513    cx.executor().advance_clock(Duration::from_millis(200));
 9514    cx.executor().start_waiting();
 9515    format_1.await.unwrap();
 9516    cx.executor().start_waiting();
 9517    format_2.await.unwrap();
 9518
 9519    // The formatting edits only happens once.
 9520    cx.assert_editor_state(indoc! {"
 9521        one
 9522            .twoˇ
 9523    "});
 9524}
 9525
 9526#[gpui::test]
 9527async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9528    init_test(cx, |settings| {
 9529        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9530    });
 9531
 9532    let mut cx = EditorLspTestContext::new_rust(
 9533        lsp::ServerCapabilities {
 9534            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9535            ..Default::default()
 9536        },
 9537        cx,
 9538    )
 9539    .await;
 9540
 9541    // Set up a buffer white some trailing whitespace and no trailing newline.
 9542    cx.set_state(
 9543        &[
 9544            "one ",   //
 9545            "twoˇ",   //
 9546            "three ", //
 9547            "four",   //
 9548        ]
 9549        .join("\n"),
 9550    );
 9551
 9552    // Submit a format request.
 9553    let format = cx
 9554        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9555        .unwrap();
 9556
 9557    // Record which buffer changes have been sent to the language server
 9558    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9559    cx.lsp
 9560        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9561            let buffer_changes = buffer_changes.clone();
 9562            move |params, _| {
 9563                buffer_changes.lock().extend(
 9564                    params
 9565                        .content_changes
 9566                        .into_iter()
 9567                        .map(|e| (e.range.unwrap(), e.text)),
 9568                );
 9569            }
 9570        });
 9571
 9572    // Handle formatting requests to the language server.
 9573    cx.lsp
 9574        .set_request_handler::<lsp::request::Formatting, _, _>({
 9575            let buffer_changes = buffer_changes.clone();
 9576            move |_, _| {
 9577                // When formatting is requested, trailing whitespace has already been stripped,
 9578                // and the trailing newline has already been added.
 9579                assert_eq!(
 9580                    &buffer_changes.lock()[1..],
 9581                    &[
 9582                        (
 9583                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9584                            "".into()
 9585                        ),
 9586                        (
 9587                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9588                            "".into()
 9589                        ),
 9590                        (
 9591                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9592                            "\n".into()
 9593                        ),
 9594                    ]
 9595                );
 9596
 9597                // Insert blank lines between each line of the buffer.
 9598                async move {
 9599                    Ok(Some(vec![
 9600                        lsp::TextEdit {
 9601                            range: lsp::Range::new(
 9602                                lsp::Position::new(1, 0),
 9603                                lsp::Position::new(1, 0),
 9604                            ),
 9605                            new_text: "\n".into(),
 9606                        },
 9607                        lsp::TextEdit {
 9608                            range: lsp::Range::new(
 9609                                lsp::Position::new(2, 0),
 9610                                lsp::Position::new(2, 0),
 9611                            ),
 9612                            new_text: "\n".into(),
 9613                        },
 9614                    ]))
 9615                }
 9616            }
 9617        });
 9618
 9619    // After formatting the buffer, the trailing whitespace is stripped,
 9620    // a newline is appended, and the edits provided by the language server
 9621    // have been applied.
 9622    format.await.unwrap();
 9623    cx.assert_editor_state(
 9624        &[
 9625            "one",   //
 9626            "",      //
 9627            "twoˇ",  //
 9628            "",      //
 9629            "three", //
 9630            "four",  //
 9631            "",      //
 9632        ]
 9633        .join("\n"),
 9634    );
 9635
 9636    // Undoing the formatting undoes the trailing whitespace removal, the
 9637    // trailing newline, and the LSP edits.
 9638    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9639    cx.assert_editor_state(
 9640        &[
 9641            "one ",   //
 9642            "twoˇ",   //
 9643            "three ", //
 9644            "four",   //
 9645        ]
 9646        .join("\n"),
 9647    );
 9648}
 9649
 9650#[gpui::test]
 9651async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9652    cx: &mut TestAppContext,
 9653) {
 9654    init_test(cx, |_| {});
 9655
 9656    cx.update(|cx| {
 9657        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9658            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9659                settings.auto_signature_help = Some(true);
 9660            });
 9661        });
 9662    });
 9663
 9664    let mut cx = EditorLspTestContext::new_rust(
 9665        lsp::ServerCapabilities {
 9666            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9667                ..Default::default()
 9668            }),
 9669            ..Default::default()
 9670        },
 9671        cx,
 9672    )
 9673    .await;
 9674
 9675    let language = Language::new(
 9676        LanguageConfig {
 9677            name: "Rust".into(),
 9678            brackets: BracketPairConfig {
 9679                pairs: vec![
 9680                    BracketPair {
 9681                        start: "{".to_string(),
 9682                        end: "}".to_string(),
 9683                        close: true,
 9684                        surround: true,
 9685                        newline: true,
 9686                    },
 9687                    BracketPair {
 9688                        start: "(".to_string(),
 9689                        end: ")".to_string(),
 9690                        close: true,
 9691                        surround: true,
 9692                        newline: true,
 9693                    },
 9694                    BracketPair {
 9695                        start: "/*".to_string(),
 9696                        end: " */".to_string(),
 9697                        close: true,
 9698                        surround: true,
 9699                        newline: true,
 9700                    },
 9701                    BracketPair {
 9702                        start: "[".to_string(),
 9703                        end: "]".to_string(),
 9704                        close: false,
 9705                        surround: false,
 9706                        newline: true,
 9707                    },
 9708                    BracketPair {
 9709                        start: "\"".to_string(),
 9710                        end: "\"".to_string(),
 9711                        close: true,
 9712                        surround: true,
 9713                        newline: false,
 9714                    },
 9715                    BracketPair {
 9716                        start: "<".to_string(),
 9717                        end: ">".to_string(),
 9718                        close: false,
 9719                        surround: true,
 9720                        newline: true,
 9721                    },
 9722                ],
 9723                ..Default::default()
 9724            },
 9725            autoclose_before: "})]".to_string(),
 9726            ..Default::default()
 9727        },
 9728        Some(tree_sitter_rust::LANGUAGE.into()),
 9729    );
 9730    let language = Arc::new(language);
 9731
 9732    cx.language_registry().add(language.clone());
 9733    cx.update_buffer(|buffer, cx| {
 9734        buffer.set_language(Some(language), cx);
 9735    });
 9736
 9737    cx.set_state(
 9738        &r#"
 9739            fn main() {
 9740                sampleˇ
 9741            }
 9742        "#
 9743        .unindent(),
 9744    );
 9745
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.handle_input("(", window, cx);
 9748    });
 9749    cx.assert_editor_state(
 9750        &"
 9751            fn main() {
 9752                sample(ˇ)
 9753            }
 9754        "
 9755        .unindent(),
 9756    );
 9757
 9758    let mocked_response = lsp::SignatureHelp {
 9759        signatures: vec![lsp::SignatureInformation {
 9760            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9761            documentation: None,
 9762            parameters: Some(vec![
 9763                lsp::ParameterInformation {
 9764                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9765                    documentation: None,
 9766                },
 9767                lsp::ParameterInformation {
 9768                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9769                    documentation: None,
 9770                },
 9771            ]),
 9772            active_parameter: None,
 9773        }],
 9774        active_signature: Some(0),
 9775        active_parameter: Some(0),
 9776    };
 9777    handle_signature_help_request(&mut cx, mocked_response).await;
 9778
 9779    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9780        .await;
 9781
 9782    cx.editor(|editor, _, _| {
 9783        let signature_help_state = editor.signature_help_state.popover().cloned();
 9784        assert_eq!(
 9785            signature_help_state.unwrap().label,
 9786            "param1: u8, param2: u8"
 9787        );
 9788    });
 9789}
 9790
 9791#[gpui::test]
 9792async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9793    init_test(cx, |_| {});
 9794
 9795    cx.update(|cx| {
 9796        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9797            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9798                settings.auto_signature_help = Some(false);
 9799                settings.show_signature_help_after_edits = Some(false);
 9800            });
 9801        });
 9802    });
 9803
 9804    let mut cx = EditorLspTestContext::new_rust(
 9805        lsp::ServerCapabilities {
 9806            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9807                ..Default::default()
 9808            }),
 9809            ..Default::default()
 9810        },
 9811        cx,
 9812    )
 9813    .await;
 9814
 9815    let language = Language::new(
 9816        LanguageConfig {
 9817            name: "Rust".into(),
 9818            brackets: BracketPairConfig {
 9819                pairs: vec![
 9820                    BracketPair {
 9821                        start: "{".to_string(),
 9822                        end: "}".to_string(),
 9823                        close: true,
 9824                        surround: true,
 9825                        newline: true,
 9826                    },
 9827                    BracketPair {
 9828                        start: "(".to_string(),
 9829                        end: ")".to_string(),
 9830                        close: true,
 9831                        surround: true,
 9832                        newline: true,
 9833                    },
 9834                    BracketPair {
 9835                        start: "/*".to_string(),
 9836                        end: " */".to_string(),
 9837                        close: true,
 9838                        surround: true,
 9839                        newline: true,
 9840                    },
 9841                    BracketPair {
 9842                        start: "[".to_string(),
 9843                        end: "]".to_string(),
 9844                        close: false,
 9845                        surround: false,
 9846                        newline: true,
 9847                    },
 9848                    BracketPair {
 9849                        start: "\"".to_string(),
 9850                        end: "\"".to_string(),
 9851                        close: true,
 9852                        surround: true,
 9853                        newline: false,
 9854                    },
 9855                    BracketPair {
 9856                        start: "<".to_string(),
 9857                        end: ">".to_string(),
 9858                        close: false,
 9859                        surround: true,
 9860                        newline: true,
 9861                    },
 9862                ],
 9863                ..Default::default()
 9864            },
 9865            autoclose_before: "})]".to_string(),
 9866            ..Default::default()
 9867        },
 9868        Some(tree_sitter_rust::LANGUAGE.into()),
 9869    );
 9870    let language = Arc::new(language);
 9871
 9872    cx.language_registry().add(language.clone());
 9873    cx.update_buffer(|buffer, cx| {
 9874        buffer.set_language(Some(language), cx);
 9875    });
 9876
 9877    // Ensure that signature_help is not called when no signature help is enabled.
 9878    cx.set_state(
 9879        &r#"
 9880            fn main() {
 9881                sampleˇ
 9882            }
 9883        "#
 9884        .unindent(),
 9885    );
 9886    cx.update_editor(|editor, window, cx| {
 9887        editor.handle_input("(", window, cx);
 9888    });
 9889    cx.assert_editor_state(
 9890        &"
 9891            fn main() {
 9892                sample(ˇ)
 9893            }
 9894        "
 9895        .unindent(),
 9896    );
 9897    cx.editor(|editor, _, _| {
 9898        assert!(editor.signature_help_state.task().is_none());
 9899    });
 9900
 9901    let mocked_response = lsp::SignatureHelp {
 9902        signatures: vec![lsp::SignatureInformation {
 9903            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9904            documentation: None,
 9905            parameters: Some(vec![
 9906                lsp::ParameterInformation {
 9907                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9908                    documentation: None,
 9909                },
 9910                lsp::ParameterInformation {
 9911                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9912                    documentation: None,
 9913                },
 9914            ]),
 9915            active_parameter: None,
 9916        }],
 9917        active_signature: Some(0),
 9918        active_parameter: Some(0),
 9919    };
 9920
 9921    // Ensure that signature_help is called when enabled afte edits
 9922    cx.update(|_, cx| {
 9923        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9924            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9925                settings.auto_signature_help = Some(false);
 9926                settings.show_signature_help_after_edits = Some(true);
 9927            });
 9928        });
 9929    });
 9930    cx.set_state(
 9931        &r#"
 9932            fn main() {
 9933                sampleˇ
 9934            }
 9935        "#
 9936        .unindent(),
 9937    );
 9938    cx.update_editor(|editor, window, cx| {
 9939        editor.handle_input("(", window, cx);
 9940    });
 9941    cx.assert_editor_state(
 9942        &"
 9943            fn main() {
 9944                sample(ˇ)
 9945            }
 9946        "
 9947        .unindent(),
 9948    );
 9949    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9950    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9951        .await;
 9952    cx.update_editor(|editor, _, _| {
 9953        let signature_help_state = editor.signature_help_state.popover().cloned();
 9954        assert!(signature_help_state.is_some());
 9955        assert_eq!(
 9956            signature_help_state.unwrap().label,
 9957            "param1: u8, param2: u8"
 9958        );
 9959        editor.signature_help_state = SignatureHelpState::default();
 9960    });
 9961
 9962    // Ensure that signature_help is called when auto signature help override is enabled
 9963    cx.update(|_, cx| {
 9964        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9965            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9966                settings.auto_signature_help = Some(true);
 9967                settings.show_signature_help_after_edits = Some(false);
 9968            });
 9969        });
 9970    });
 9971    cx.set_state(
 9972        &r#"
 9973            fn main() {
 9974                sampleˇ
 9975            }
 9976        "#
 9977        .unindent(),
 9978    );
 9979    cx.update_editor(|editor, window, cx| {
 9980        editor.handle_input("(", window, cx);
 9981    });
 9982    cx.assert_editor_state(
 9983        &"
 9984            fn main() {
 9985                sample(ˇ)
 9986            }
 9987        "
 9988        .unindent(),
 9989    );
 9990    handle_signature_help_request(&mut cx, mocked_response).await;
 9991    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9992        .await;
 9993    cx.editor(|editor, _, _| {
 9994        let signature_help_state = editor.signature_help_state.popover().cloned();
 9995        assert!(signature_help_state.is_some());
 9996        assert_eq!(
 9997            signature_help_state.unwrap().label,
 9998            "param1: u8, param2: u8"
 9999        );
10000    });
10001}
10002
10003#[gpui::test]
10004async fn test_signature_help(cx: &mut TestAppContext) {
10005    init_test(cx, |_| {});
10006    cx.update(|cx| {
10007        cx.update_global::<SettingsStore, _>(|settings, cx| {
10008            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10009                settings.auto_signature_help = Some(true);
10010            });
10011        });
10012    });
10013
10014    let mut cx = EditorLspTestContext::new_rust(
10015        lsp::ServerCapabilities {
10016            signature_help_provider: Some(lsp::SignatureHelpOptions {
10017                ..Default::default()
10018            }),
10019            ..Default::default()
10020        },
10021        cx,
10022    )
10023    .await;
10024
10025    // A test that directly calls `show_signature_help`
10026    cx.update_editor(|editor, window, cx| {
10027        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10028    });
10029
10030    let mocked_response = lsp::SignatureHelp {
10031        signatures: vec![lsp::SignatureInformation {
10032            label: "fn sample(param1: u8, param2: u8)".to_string(),
10033            documentation: None,
10034            parameters: Some(vec![
10035                lsp::ParameterInformation {
10036                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10037                    documentation: None,
10038                },
10039                lsp::ParameterInformation {
10040                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10041                    documentation: None,
10042                },
10043            ]),
10044            active_parameter: None,
10045        }],
10046        active_signature: Some(0),
10047        active_parameter: Some(0),
10048    };
10049    handle_signature_help_request(&mut cx, mocked_response).await;
10050
10051    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10052        .await;
10053
10054    cx.editor(|editor, _, _| {
10055        let signature_help_state = editor.signature_help_state.popover().cloned();
10056        assert!(signature_help_state.is_some());
10057        assert_eq!(
10058            signature_help_state.unwrap().label,
10059            "param1: u8, param2: u8"
10060        );
10061    });
10062
10063    // When exiting outside from inside the brackets, `signature_help` is closed.
10064    cx.set_state(indoc! {"
10065        fn main() {
10066            sample(ˇ);
10067        }
10068
10069        fn sample(param1: u8, param2: u8) {}
10070    "});
10071
10072    cx.update_editor(|editor, window, cx| {
10073        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10074    });
10075
10076    let mocked_response = lsp::SignatureHelp {
10077        signatures: Vec::new(),
10078        active_signature: None,
10079        active_parameter: None,
10080    };
10081    handle_signature_help_request(&mut cx, mocked_response).await;
10082
10083    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10084        .await;
10085
10086    cx.editor(|editor, _, _| {
10087        assert!(!editor.signature_help_state.is_shown());
10088    });
10089
10090    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10091    cx.set_state(indoc! {"
10092        fn main() {
10093            sample(ˇ);
10094        }
10095
10096        fn sample(param1: u8, param2: u8) {}
10097    "});
10098
10099    let mocked_response = lsp::SignatureHelp {
10100        signatures: vec![lsp::SignatureInformation {
10101            label: "fn sample(param1: u8, param2: u8)".to_string(),
10102            documentation: None,
10103            parameters: Some(vec![
10104                lsp::ParameterInformation {
10105                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10106                    documentation: None,
10107                },
10108                lsp::ParameterInformation {
10109                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10110                    documentation: None,
10111                },
10112            ]),
10113            active_parameter: None,
10114        }],
10115        active_signature: Some(0),
10116        active_parameter: Some(0),
10117    };
10118    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10119    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10120        .await;
10121    cx.editor(|editor, _, _| {
10122        assert!(editor.signature_help_state.is_shown());
10123    });
10124
10125    // Restore the popover with more parameter input
10126    cx.set_state(indoc! {"
10127        fn main() {
10128            sample(param1, param2ˇ);
10129        }
10130
10131        fn sample(param1: u8, param2: u8) {}
10132    "});
10133
10134    let mocked_response = lsp::SignatureHelp {
10135        signatures: vec![lsp::SignatureInformation {
10136            label: "fn sample(param1: u8, param2: u8)".to_string(),
10137            documentation: None,
10138            parameters: Some(vec![
10139                lsp::ParameterInformation {
10140                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10141                    documentation: None,
10142                },
10143                lsp::ParameterInformation {
10144                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10145                    documentation: None,
10146                },
10147            ]),
10148            active_parameter: None,
10149        }],
10150        active_signature: Some(0),
10151        active_parameter: Some(1),
10152    };
10153    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10154    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10155        .await;
10156
10157    // When selecting a range, the popover is gone.
10158    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10159    cx.update_editor(|editor, window, cx| {
10160        editor.change_selections(None, window, cx, |s| {
10161            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10162        })
10163    });
10164    cx.assert_editor_state(indoc! {"
10165        fn main() {
10166            sample(param1, «ˇparam2»);
10167        }
10168
10169        fn sample(param1: u8, param2: u8) {}
10170    "});
10171    cx.editor(|editor, _, _| {
10172        assert!(!editor.signature_help_state.is_shown());
10173    });
10174
10175    // When unselecting again, the popover is back if within the brackets.
10176    cx.update_editor(|editor, window, cx| {
10177        editor.change_selections(None, window, cx, |s| {
10178            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10179        })
10180    });
10181    cx.assert_editor_state(indoc! {"
10182        fn main() {
10183            sample(param1, ˇparam2);
10184        }
10185
10186        fn sample(param1: u8, param2: u8) {}
10187    "});
10188    handle_signature_help_request(&mut cx, mocked_response).await;
10189    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10190        .await;
10191    cx.editor(|editor, _, _| {
10192        assert!(editor.signature_help_state.is_shown());
10193    });
10194
10195    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10196    cx.update_editor(|editor, window, cx| {
10197        editor.change_selections(None, window, cx, |s| {
10198            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10199            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10200        })
10201    });
10202    cx.assert_editor_state(indoc! {"
10203        fn main() {
10204            sample(param1, ˇparam2);
10205        }
10206
10207        fn sample(param1: u8, param2: u8) {}
10208    "});
10209
10210    let mocked_response = lsp::SignatureHelp {
10211        signatures: vec![lsp::SignatureInformation {
10212            label: "fn sample(param1: u8, param2: u8)".to_string(),
10213            documentation: None,
10214            parameters: Some(vec![
10215                lsp::ParameterInformation {
10216                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10217                    documentation: None,
10218                },
10219                lsp::ParameterInformation {
10220                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10221                    documentation: None,
10222                },
10223            ]),
10224            active_parameter: None,
10225        }],
10226        active_signature: Some(0),
10227        active_parameter: Some(1),
10228    };
10229    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10230    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10231        .await;
10232    cx.update_editor(|editor, _, cx| {
10233        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10234    });
10235    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10236        .await;
10237    cx.update_editor(|editor, window, cx| {
10238        editor.change_selections(None, window, cx, |s| {
10239            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10240        })
10241    });
10242    cx.assert_editor_state(indoc! {"
10243        fn main() {
10244            sample(param1, «ˇparam2»);
10245        }
10246
10247        fn sample(param1: u8, param2: u8) {}
10248    "});
10249    cx.update_editor(|editor, window, cx| {
10250        editor.change_selections(None, window, cx, |s| {
10251            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10252        })
10253    });
10254    cx.assert_editor_state(indoc! {"
10255        fn main() {
10256            sample(param1, ˇparam2);
10257        }
10258
10259        fn sample(param1: u8, param2: u8) {}
10260    "});
10261    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10262        .await;
10263}
10264
10265#[gpui::test]
10266async fn test_completion_mode(cx: &mut TestAppContext) {
10267    init_test(cx, |_| {});
10268    let mut cx = EditorLspTestContext::new_rust(
10269        lsp::ServerCapabilities {
10270            completion_provider: Some(lsp::CompletionOptions {
10271                resolve_provider: Some(true),
10272                ..Default::default()
10273            }),
10274            ..Default::default()
10275        },
10276        cx,
10277    )
10278    .await;
10279
10280    struct Run {
10281        run_description: &'static str,
10282        initial_state: String,
10283        buffer_marked_text: String,
10284        completion_text: &'static str,
10285        expected_with_insert_mode: String,
10286        expected_with_replace_mode: String,
10287        expected_with_replace_subsequence_mode: String,
10288        expected_with_replace_suffix_mode: String,
10289    }
10290
10291    let runs = [
10292        Run {
10293            run_description: "Start of word matches completion text",
10294            initial_state: "before ediˇ after".into(),
10295            buffer_marked_text: "before <edi|> after".into(),
10296            completion_text: "editor",
10297            expected_with_insert_mode: "before editorˇ after".into(),
10298            expected_with_replace_mode: "before editorˇ after".into(),
10299            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10300            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10301        },
10302        Run {
10303            run_description: "Accept same text at the middle of the word",
10304            initial_state: "before ediˇtor after".into(),
10305            buffer_marked_text: "before <edi|tor> after".into(),
10306            completion_text: "editor",
10307            expected_with_insert_mode: "before editorˇtor after".into(),
10308            expected_with_replace_mode: "before editorˇ after".into(),
10309            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10310            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10311        },
10312        Run {
10313            run_description: "End of word matches completion text -- cursor at end",
10314            initial_state: "before torˇ after".into(),
10315            buffer_marked_text: "before <tor|> after".into(),
10316            completion_text: "editor",
10317            expected_with_insert_mode: "before editorˇ after".into(),
10318            expected_with_replace_mode: "before editorˇ after".into(),
10319            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10320            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10321        },
10322        Run {
10323            run_description: "End of word matches completion text -- cursor at start",
10324            initial_state: "before ˇtor after".into(),
10325            buffer_marked_text: "before <|tor> after".into(),
10326            completion_text: "editor",
10327            expected_with_insert_mode: "before editorˇtor after".into(),
10328            expected_with_replace_mode: "before editorˇ after".into(),
10329            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10330            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10331        },
10332        Run {
10333            run_description: "Prepend text containing whitespace",
10334            initial_state: "pˇfield: bool".into(),
10335            buffer_marked_text: "<p|field>: bool".into(),
10336            completion_text: "pub ",
10337            expected_with_insert_mode: "pub ˇfield: bool".into(),
10338            expected_with_replace_mode: "pub ˇ: bool".into(),
10339            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10340            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10341        },
10342        Run {
10343            run_description: "Add element to start of list",
10344            initial_state: "[element_ˇelement_2]".into(),
10345            buffer_marked_text: "[<element_|element_2>]".into(),
10346            completion_text: "element_1",
10347            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10348            expected_with_replace_mode: "[element_1ˇ]".into(),
10349            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10350            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10351        },
10352        Run {
10353            run_description: "Add element to start of list -- first and second elements are equal",
10354            initial_state: "[elˇelement]".into(),
10355            buffer_marked_text: "[<el|element>]".into(),
10356            completion_text: "element",
10357            expected_with_insert_mode: "[elementˇelement]".into(),
10358            expected_with_replace_mode: "[elementˇ]".into(),
10359            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10360            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10361        },
10362        Run {
10363            run_description: "Ends with matching suffix",
10364            initial_state: "SubˇError".into(),
10365            buffer_marked_text: "<Sub|Error>".into(),
10366            completion_text: "SubscriptionError",
10367            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10368            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10369            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10370            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10371        },
10372        Run {
10373            run_description: "Suffix is a subsequence -- contiguous",
10374            initial_state: "SubˇErr".into(),
10375            buffer_marked_text: "<Sub|Err>".into(),
10376            completion_text: "SubscriptionError",
10377            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10378            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10379            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10380            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10381        },
10382        Run {
10383            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10384            initial_state: "Suˇscrirr".into(),
10385            buffer_marked_text: "<Su|scrirr>".into(),
10386            completion_text: "SubscriptionError",
10387            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10388            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10389            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10390            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10391        },
10392        Run {
10393            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10394            initial_state: "foo(indˇix)".into(),
10395            buffer_marked_text: "foo(<ind|ix>)".into(),
10396            completion_text: "node_index",
10397            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10398            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10399            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10400            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10401        },
10402    ];
10403
10404    for run in runs {
10405        let run_variations = [
10406            (LspInsertMode::Insert, run.expected_with_insert_mode),
10407            (LspInsertMode::Replace, run.expected_with_replace_mode),
10408            (
10409                LspInsertMode::ReplaceSubsequence,
10410                run.expected_with_replace_subsequence_mode,
10411            ),
10412            (
10413                LspInsertMode::ReplaceSuffix,
10414                run.expected_with_replace_suffix_mode,
10415            ),
10416        ];
10417
10418        for (lsp_insert_mode, expected_text) in run_variations {
10419            eprintln!(
10420                "run = {:?}, mode = {lsp_insert_mode:.?}",
10421                run.run_description,
10422            );
10423
10424            update_test_language_settings(&mut cx, |settings| {
10425                settings.defaults.completions = Some(CompletionSettings {
10426                    lsp_insert_mode,
10427                    words: WordsCompletionMode::Disabled,
10428                    lsp: true,
10429                    lsp_fetch_timeout_ms: 0,
10430                });
10431            });
10432
10433            cx.set_state(&run.initial_state);
10434            cx.update_editor(|editor, window, cx| {
10435                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10436            });
10437
10438            let counter = Arc::new(AtomicUsize::new(0));
10439            handle_completion_request_with_insert_and_replace(
10440                &mut cx,
10441                &run.buffer_marked_text,
10442                vec![run.completion_text],
10443                counter.clone(),
10444            )
10445            .await;
10446            cx.condition(|editor, _| editor.context_menu_visible())
10447                .await;
10448            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10449
10450            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10451                editor
10452                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10453                    .unwrap()
10454            });
10455            cx.assert_editor_state(&expected_text);
10456            handle_resolve_completion_request(&mut cx, None).await;
10457            apply_additional_edits.await.unwrap();
10458        }
10459    }
10460}
10461
10462#[gpui::test]
10463async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10464    init_test(cx, |_| {});
10465    let mut cx = EditorLspTestContext::new_rust(
10466        lsp::ServerCapabilities {
10467            completion_provider: Some(lsp::CompletionOptions {
10468                resolve_provider: Some(true),
10469                ..Default::default()
10470            }),
10471            ..Default::default()
10472        },
10473        cx,
10474    )
10475    .await;
10476
10477    let initial_state = "SubˇError";
10478    let buffer_marked_text = "<Sub|Error>";
10479    let completion_text = "SubscriptionError";
10480    let expected_with_insert_mode = "SubscriptionErrorˇError";
10481    let expected_with_replace_mode = "SubscriptionErrorˇ";
10482
10483    update_test_language_settings(&mut cx, |settings| {
10484        settings.defaults.completions = Some(CompletionSettings {
10485            words: WordsCompletionMode::Disabled,
10486            // set the opposite here to ensure that the action is overriding the default behavior
10487            lsp_insert_mode: LspInsertMode::Insert,
10488            lsp: true,
10489            lsp_fetch_timeout_ms: 0,
10490        });
10491    });
10492
10493    cx.set_state(initial_state);
10494    cx.update_editor(|editor, window, cx| {
10495        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10496    });
10497
10498    let counter = Arc::new(AtomicUsize::new(0));
10499    handle_completion_request_with_insert_and_replace(
10500        &mut cx,
10501        &buffer_marked_text,
10502        vec![completion_text],
10503        counter.clone(),
10504    )
10505    .await;
10506    cx.condition(|editor, _| editor.context_menu_visible())
10507        .await;
10508    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10509
10510    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10511        editor
10512            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10513            .unwrap()
10514    });
10515    cx.assert_editor_state(&expected_with_replace_mode);
10516    handle_resolve_completion_request(&mut cx, None).await;
10517    apply_additional_edits.await.unwrap();
10518
10519    update_test_language_settings(&mut cx, |settings| {
10520        settings.defaults.completions = Some(CompletionSettings {
10521            words: WordsCompletionMode::Disabled,
10522            // set the opposite here to ensure that the action is overriding the default behavior
10523            lsp_insert_mode: LspInsertMode::Replace,
10524            lsp: true,
10525            lsp_fetch_timeout_ms: 0,
10526        });
10527    });
10528
10529    cx.set_state(initial_state);
10530    cx.update_editor(|editor, window, cx| {
10531        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10532    });
10533    handle_completion_request_with_insert_and_replace(
10534        &mut cx,
10535        &buffer_marked_text,
10536        vec![completion_text],
10537        counter.clone(),
10538    )
10539    .await;
10540    cx.condition(|editor, _| editor.context_menu_visible())
10541        .await;
10542    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10543
10544    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10545        editor
10546            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10547            .unwrap()
10548    });
10549    cx.assert_editor_state(&expected_with_insert_mode);
10550    handle_resolve_completion_request(&mut cx, None).await;
10551    apply_additional_edits.await.unwrap();
10552}
10553
10554#[gpui::test]
10555async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10556    init_test(cx, |_| {});
10557    let mut cx = EditorLspTestContext::new_rust(
10558        lsp::ServerCapabilities {
10559            completion_provider: Some(lsp::CompletionOptions {
10560                resolve_provider: Some(true),
10561                ..Default::default()
10562            }),
10563            ..Default::default()
10564        },
10565        cx,
10566    )
10567    .await;
10568
10569    // scenario: surrounding text matches completion text
10570    let completion_text = "to_offset";
10571    let initial_state = indoc! {"
10572        1. buf.to_offˇsuffix
10573        2. buf.to_offˇsuf
10574        3. buf.to_offˇfix
10575        4. buf.to_offˇ
10576        5. into_offˇensive
10577        6. ˇsuffix
10578        7. let ˇ //
10579        8. aaˇzz
10580        9. buf.to_off«zzzzzˇ»suffix
10581        10. buf.«ˇzzzzz»suffix
10582        11. to_off«ˇzzzzz»
10583
10584        buf.to_offˇsuffix  // newest cursor
10585    "};
10586    let completion_marked_buffer = indoc! {"
10587        1. buf.to_offsuffix
10588        2. buf.to_offsuf
10589        3. buf.to_offfix
10590        4. buf.to_off
10591        5. into_offensive
10592        6. suffix
10593        7. let  //
10594        8. aazz
10595        9. buf.to_offzzzzzsuffix
10596        10. buf.zzzzzsuffix
10597        11. to_offzzzzz
10598
10599        buf.<to_off|suffix>  // newest cursor
10600    "};
10601    let expected = indoc! {"
10602        1. buf.to_offsetˇ
10603        2. buf.to_offsetˇsuf
10604        3. buf.to_offsetˇfix
10605        4. buf.to_offsetˇ
10606        5. into_offsetˇensive
10607        6. to_offsetˇsuffix
10608        7. let to_offsetˇ //
10609        8. aato_offsetˇzz
10610        9. buf.to_offsetˇ
10611        10. buf.to_offsetˇsuffix
10612        11. to_offsetˇ
10613
10614        buf.to_offsetˇ  // newest cursor
10615    "};
10616    cx.set_state(initial_state);
10617    cx.update_editor(|editor, window, cx| {
10618        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10619    });
10620    handle_completion_request_with_insert_and_replace(
10621        &mut cx,
10622        completion_marked_buffer,
10623        vec![completion_text],
10624        Arc::new(AtomicUsize::new(0)),
10625    )
10626    .await;
10627    cx.condition(|editor, _| editor.context_menu_visible())
10628        .await;
10629    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10630        editor
10631            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10632            .unwrap()
10633    });
10634    cx.assert_editor_state(expected);
10635    handle_resolve_completion_request(&mut cx, None).await;
10636    apply_additional_edits.await.unwrap();
10637
10638    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10639    let completion_text = "foo_and_bar";
10640    let initial_state = indoc! {"
10641        1. ooanbˇ
10642        2. zooanbˇ
10643        3. ooanbˇz
10644        4. zooanbˇz
10645        5. ooanˇ
10646        6. oanbˇ
10647
10648        ooanbˇ
10649    "};
10650    let completion_marked_buffer = indoc! {"
10651        1. ooanb
10652        2. zooanb
10653        3. ooanbz
10654        4. zooanbz
10655        5. ooan
10656        6. oanb
10657
10658        <ooanb|>
10659    "};
10660    let expected = indoc! {"
10661        1. foo_and_barˇ
10662        2. zfoo_and_barˇ
10663        3. foo_and_barˇz
10664        4. zfoo_and_barˇz
10665        5. ooanfoo_and_barˇ
10666        6. oanbfoo_and_barˇ
10667
10668        foo_and_barˇ
10669    "};
10670    cx.set_state(initial_state);
10671    cx.update_editor(|editor, window, cx| {
10672        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10673    });
10674    handle_completion_request_with_insert_and_replace(
10675        &mut cx,
10676        completion_marked_buffer,
10677        vec![completion_text],
10678        Arc::new(AtomicUsize::new(0)),
10679    )
10680    .await;
10681    cx.condition(|editor, _| editor.context_menu_visible())
10682        .await;
10683    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10684        editor
10685            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10686            .unwrap()
10687    });
10688    cx.assert_editor_state(expected);
10689    handle_resolve_completion_request(&mut cx, None).await;
10690    apply_additional_edits.await.unwrap();
10691
10692    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10693    // (expects the same as if it was inserted at the end)
10694    let completion_text = "foo_and_bar";
10695    let initial_state = indoc! {"
10696        1. ooˇanb
10697        2. zooˇanb
10698        3. ooˇanbz
10699        4. zooˇanbz
10700
10701        ooˇanb
10702    "};
10703    let completion_marked_buffer = indoc! {"
10704        1. ooanb
10705        2. zooanb
10706        3. ooanbz
10707        4. zooanbz
10708
10709        <oo|anb>
10710    "};
10711    let expected = indoc! {"
10712        1. foo_and_barˇ
10713        2. zfoo_and_barˇ
10714        3. foo_and_barˇz
10715        4. zfoo_and_barˇz
10716
10717        foo_and_barˇ
10718    "};
10719    cx.set_state(initial_state);
10720    cx.update_editor(|editor, window, cx| {
10721        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10722    });
10723    handle_completion_request_with_insert_and_replace(
10724        &mut cx,
10725        completion_marked_buffer,
10726        vec![completion_text],
10727        Arc::new(AtomicUsize::new(0)),
10728    )
10729    .await;
10730    cx.condition(|editor, _| editor.context_menu_visible())
10731        .await;
10732    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10733        editor
10734            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10735            .unwrap()
10736    });
10737    cx.assert_editor_state(expected);
10738    handle_resolve_completion_request(&mut cx, None).await;
10739    apply_additional_edits.await.unwrap();
10740}
10741
10742// This used to crash
10743#[gpui::test]
10744async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10745    init_test(cx, |_| {});
10746
10747    let buffer_text = indoc! {"
10748        fn main() {
10749            10.satu;
10750
10751            //
10752            // separate cursors so they open in different excerpts (manually reproducible)
10753            //
10754
10755            10.satu20;
10756        }
10757    "};
10758    let multibuffer_text_with_selections = indoc! {"
10759        fn main() {
10760            10.satuˇ;
10761
10762            //
10763
10764            //
10765
10766            10.satuˇ20;
10767        }
10768    "};
10769    let expected_multibuffer = indoc! {"
10770        fn main() {
10771            10.saturating_sub()ˇ;
10772
10773            //
10774
10775            //
10776
10777            10.saturating_sub()ˇ;
10778        }
10779    "};
10780
10781    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10782    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10783
10784    let fs = FakeFs::new(cx.executor());
10785    fs.insert_tree(
10786        path!("/a"),
10787        json!({
10788            "main.rs": buffer_text,
10789        }),
10790    )
10791    .await;
10792
10793    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10794    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10795    language_registry.add(rust_lang());
10796    let mut fake_servers = language_registry.register_fake_lsp(
10797        "Rust",
10798        FakeLspAdapter {
10799            capabilities: lsp::ServerCapabilities {
10800                completion_provider: Some(lsp::CompletionOptions {
10801                    resolve_provider: None,
10802                    ..lsp::CompletionOptions::default()
10803                }),
10804                ..lsp::ServerCapabilities::default()
10805            },
10806            ..FakeLspAdapter::default()
10807        },
10808    );
10809    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10810    let cx = &mut VisualTestContext::from_window(*workspace, cx);
10811    let buffer = project
10812        .update(cx, |project, cx| {
10813            project.open_local_buffer(path!("/a/main.rs"), cx)
10814        })
10815        .await
10816        .unwrap();
10817
10818    let multi_buffer = cx.new(|cx| {
10819        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
10820        multi_buffer.push_excerpts(
10821            buffer.clone(),
10822            [ExcerptRange::new(0..first_excerpt_end)],
10823            cx,
10824        );
10825        multi_buffer.push_excerpts(
10826            buffer.clone(),
10827            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
10828            cx,
10829        );
10830        multi_buffer
10831    });
10832
10833    let editor = workspace
10834        .update(cx, |_, window, cx| {
10835            cx.new(|cx| {
10836                Editor::new(
10837                    EditorMode::Full {
10838                        scale_ui_elements_with_buffer_font_size: false,
10839                        show_active_line_background: false,
10840                        sized_by_content: false,
10841                    },
10842                    multi_buffer.clone(),
10843                    Some(project.clone()),
10844                    window,
10845                    cx,
10846                )
10847            })
10848        })
10849        .unwrap();
10850
10851    let pane = workspace
10852        .update(cx, |workspace, _, _| workspace.active_pane().clone())
10853        .unwrap();
10854    pane.update_in(cx, |pane, window, cx| {
10855        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
10856    });
10857
10858    let fake_server = fake_servers.next().await.unwrap();
10859
10860    editor.update_in(cx, |editor, window, cx| {
10861        editor.change_selections(None, window, cx, |s| {
10862            s.select_ranges([
10863                Point::new(1, 11)..Point::new(1, 11),
10864                Point::new(7, 11)..Point::new(7, 11),
10865            ])
10866        });
10867
10868        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
10869    });
10870
10871    editor.update_in(cx, |editor, window, cx| {
10872        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10873    });
10874
10875    fake_server
10876        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
10877            let completion_item = lsp::CompletionItem {
10878                label: "saturating_sub()".into(),
10879                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10880                    lsp::InsertReplaceEdit {
10881                        new_text: "saturating_sub()".to_owned(),
10882                        insert: lsp::Range::new(
10883                            lsp::Position::new(7, 7),
10884                            lsp::Position::new(7, 11),
10885                        ),
10886                        replace: lsp::Range::new(
10887                            lsp::Position::new(7, 7),
10888                            lsp::Position::new(7, 13),
10889                        ),
10890                    },
10891                )),
10892                ..lsp::CompletionItem::default()
10893            };
10894
10895            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
10896        })
10897        .next()
10898        .await
10899        .unwrap();
10900
10901    cx.condition(&editor, |editor, _| editor.context_menu_visible())
10902        .await;
10903
10904    editor
10905        .update_in(cx, |editor, window, cx| {
10906            editor
10907                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10908                .unwrap()
10909        })
10910        .await
10911        .unwrap();
10912
10913    editor.update(cx, |editor, cx| {
10914        assert_text_with_selections(editor, expected_multibuffer, cx);
10915    })
10916}
10917
10918#[gpui::test]
10919async fn test_completion(cx: &mut TestAppContext) {
10920    init_test(cx, |_| {});
10921
10922    let mut cx = EditorLspTestContext::new_rust(
10923        lsp::ServerCapabilities {
10924            completion_provider: Some(lsp::CompletionOptions {
10925                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
10926                resolve_provider: Some(true),
10927                ..Default::default()
10928            }),
10929            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
10930            ..Default::default()
10931        },
10932        cx,
10933    )
10934    .await;
10935    let counter = Arc::new(AtomicUsize::new(0));
10936
10937    cx.set_state(indoc! {"
10938        oneˇ
10939        two
10940        three
10941    "});
10942    cx.simulate_keystroke(".");
10943    handle_completion_request(
10944        &mut cx,
10945        indoc! {"
10946            one.|<>
10947            two
10948            three
10949        "},
10950        vec!["first_completion", "second_completion"],
10951        counter.clone(),
10952    )
10953    .await;
10954    cx.condition(|editor, _| editor.context_menu_visible())
10955        .await;
10956    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10957
10958    let _handler = handle_signature_help_request(
10959        &mut cx,
10960        lsp::SignatureHelp {
10961            signatures: vec![lsp::SignatureInformation {
10962                label: "test signature".to_string(),
10963                documentation: None,
10964                parameters: Some(vec![lsp::ParameterInformation {
10965                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
10966                    documentation: None,
10967                }]),
10968                active_parameter: None,
10969            }],
10970            active_signature: None,
10971            active_parameter: None,
10972        },
10973    );
10974    cx.update_editor(|editor, window, cx| {
10975        assert!(
10976            !editor.signature_help_state.is_shown(),
10977            "No signature help was called for"
10978        );
10979        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10980    });
10981    cx.run_until_parked();
10982    cx.update_editor(|editor, _, _| {
10983        assert!(
10984            !editor.signature_help_state.is_shown(),
10985            "No signature help should be shown when completions menu is open"
10986        );
10987    });
10988
10989    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10990        editor.context_menu_next(&Default::default(), window, cx);
10991        editor
10992            .confirm_completion(&ConfirmCompletion::default(), window, cx)
10993            .unwrap()
10994    });
10995    cx.assert_editor_state(indoc! {"
10996        one.second_completionˇ
10997        two
10998        three
10999    "});
11000
11001    handle_resolve_completion_request(
11002        &mut cx,
11003        Some(vec![
11004            (
11005                //This overlaps with the primary completion edit which is
11006                //misbehavior from the LSP spec, test that we filter it out
11007                indoc! {"
11008                    one.second_ˇcompletion
11009                    two
11010                    threeˇ
11011                "},
11012                "overlapping additional edit",
11013            ),
11014            (
11015                indoc! {"
11016                    one.second_completion
11017                    two
11018                    threeˇ
11019                "},
11020                "\nadditional edit",
11021            ),
11022        ]),
11023    )
11024    .await;
11025    apply_additional_edits.await.unwrap();
11026    cx.assert_editor_state(indoc! {"
11027        one.second_completionˇ
11028        two
11029        three
11030        additional edit
11031    "});
11032
11033    cx.set_state(indoc! {"
11034        one.second_completion
11035        twoˇ
11036        threeˇ
11037        additional edit
11038    "});
11039    cx.simulate_keystroke(" ");
11040    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11041    cx.simulate_keystroke("s");
11042    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11043
11044    cx.assert_editor_state(indoc! {"
11045        one.second_completion
11046        two sˇ
11047        three sˇ
11048        additional edit
11049    "});
11050    handle_completion_request(
11051        &mut cx,
11052        indoc! {"
11053            one.second_completion
11054            two s
11055            three <s|>
11056            additional edit
11057        "},
11058        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11059        counter.clone(),
11060    )
11061    .await;
11062    cx.condition(|editor, _| editor.context_menu_visible())
11063        .await;
11064    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11065
11066    cx.simulate_keystroke("i");
11067
11068    handle_completion_request(
11069        &mut cx,
11070        indoc! {"
11071            one.second_completion
11072            two si
11073            three <si|>
11074            additional edit
11075        "},
11076        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11077        counter.clone(),
11078    )
11079    .await;
11080    cx.condition(|editor, _| editor.context_menu_visible())
11081        .await;
11082    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11083
11084    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11085        editor
11086            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11087            .unwrap()
11088    });
11089    cx.assert_editor_state(indoc! {"
11090        one.second_completion
11091        two sixth_completionˇ
11092        three sixth_completionˇ
11093        additional edit
11094    "});
11095
11096    apply_additional_edits.await.unwrap();
11097
11098    update_test_language_settings(&mut cx, |settings| {
11099        settings.defaults.show_completions_on_input = Some(false);
11100    });
11101    cx.set_state("editorˇ");
11102    cx.simulate_keystroke(".");
11103    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11104    cx.simulate_keystrokes("c l o");
11105    cx.assert_editor_state("editor.cloˇ");
11106    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11107    cx.update_editor(|editor, window, cx| {
11108        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11109    });
11110    handle_completion_request(
11111        &mut cx,
11112        "editor.<clo|>",
11113        vec!["close", "clobber"],
11114        counter.clone(),
11115    )
11116    .await;
11117    cx.condition(|editor, _| editor.context_menu_visible())
11118        .await;
11119    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11120
11121    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11122        editor
11123            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11124            .unwrap()
11125    });
11126    cx.assert_editor_state("editor.closeˇ");
11127    handle_resolve_completion_request(&mut cx, None).await;
11128    apply_additional_edits.await.unwrap();
11129}
11130
11131#[gpui::test]
11132async fn test_word_completion(cx: &mut TestAppContext) {
11133    let lsp_fetch_timeout_ms = 10;
11134    init_test(cx, |language_settings| {
11135        language_settings.defaults.completions = Some(CompletionSettings {
11136            words: WordsCompletionMode::Fallback,
11137            lsp: true,
11138            lsp_fetch_timeout_ms: 10,
11139            lsp_insert_mode: LspInsertMode::Insert,
11140        });
11141    });
11142
11143    let mut cx = EditorLspTestContext::new_rust(
11144        lsp::ServerCapabilities {
11145            completion_provider: Some(lsp::CompletionOptions {
11146                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11147                ..lsp::CompletionOptions::default()
11148            }),
11149            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11150            ..lsp::ServerCapabilities::default()
11151        },
11152        cx,
11153    )
11154    .await;
11155
11156    let throttle_completions = Arc::new(AtomicBool::new(false));
11157
11158    let lsp_throttle_completions = throttle_completions.clone();
11159    let _completion_requests_handler =
11160        cx.lsp
11161            .server
11162            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11163                let lsp_throttle_completions = lsp_throttle_completions.clone();
11164                let cx = cx.clone();
11165                async move {
11166                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11167                        cx.background_executor()
11168                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11169                            .await;
11170                    }
11171                    Ok(Some(lsp::CompletionResponse::Array(vec![
11172                        lsp::CompletionItem {
11173                            label: "first".into(),
11174                            ..lsp::CompletionItem::default()
11175                        },
11176                        lsp::CompletionItem {
11177                            label: "last".into(),
11178                            ..lsp::CompletionItem::default()
11179                        },
11180                    ])))
11181                }
11182            });
11183
11184    cx.set_state(indoc! {"
11185        oneˇ
11186        two
11187        three
11188    "});
11189    cx.simulate_keystroke(".");
11190    cx.executor().run_until_parked();
11191    cx.condition(|editor, _| editor.context_menu_visible())
11192        .await;
11193    cx.update_editor(|editor, window, cx| {
11194        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11195        {
11196            assert_eq!(
11197                completion_menu_entries(&menu),
11198                &["first", "last"],
11199                "When LSP server is fast to reply, no fallback word completions are used"
11200            );
11201        } else {
11202            panic!("expected completion menu to be open");
11203        }
11204        editor.cancel(&Cancel, window, cx);
11205    });
11206    cx.executor().run_until_parked();
11207    cx.condition(|editor, _| !editor.context_menu_visible())
11208        .await;
11209
11210    throttle_completions.store(true, atomic::Ordering::Release);
11211    cx.simulate_keystroke(".");
11212    cx.executor()
11213        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11214    cx.executor().run_until_parked();
11215    cx.condition(|editor, _| editor.context_menu_visible())
11216        .await;
11217    cx.update_editor(|editor, _, _| {
11218        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11219        {
11220            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11221                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11222        } else {
11223            panic!("expected completion menu to be open");
11224        }
11225    });
11226}
11227
11228#[gpui::test]
11229async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11230    init_test(cx, |language_settings| {
11231        language_settings.defaults.completions = Some(CompletionSettings {
11232            words: WordsCompletionMode::Enabled,
11233            lsp: true,
11234            lsp_fetch_timeout_ms: 0,
11235            lsp_insert_mode: LspInsertMode::Insert,
11236        });
11237    });
11238
11239    let mut cx = EditorLspTestContext::new_rust(
11240        lsp::ServerCapabilities {
11241            completion_provider: Some(lsp::CompletionOptions {
11242                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11243                ..lsp::CompletionOptions::default()
11244            }),
11245            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11246            ..lsp::ServerCapabilities::default()
11247        },
11248        cx,
11249    )
11250    .await;
11251
11252    let _completion_requests_handler =
11253        cx.lsp
11254            .server
11255            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11256                Ok(Some(lsp::CompletionResponse::Array(vec![
11257                    lsp::CompletionItem {
11258                        label: "first".into(),
11259                        ..lsp::CompletionItem::default()
11260                    },
11261                    lsp::CompletionItem {
11262                        label: "last".into(),
11263                        ..lsp::CompletionItem::default()
11264                    },
11265                ])))
11266            });
11267
11268    cx.set_state(indoc! {"ˇ
11269        first
11270        last
11271        second
11272    "});
11273    cx.simulate_keystroke(".");
11274    cx.executor().run_until_parked();
11275    cx.condition(|editor, _| editor.context_menu_visible())
11276        .await;
11277    cx.update_editor(|editor, _, _| {
11278        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11279        {
11280            assert_eq!(
11281                completion_menu_entries(&menu),
11282                &["first", "last", "second"],
11283                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11284            );
11285        } else {
11286            panic!("expected completion menu to be open");
11287        }
11288    });
11289}
11290
11291#[gpui::test]
11292async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11293    init_test(cx, |language_settings| {
11294        language_settings.defaults.completions = Some(CompletionSettings {
11295            words: WordsCompletionMode::Disabled,
11296            lsp: true,
11297            lsp_fetch_timeout_ms: 0,
11298            lsp_insert_mode: LspInsertMode::Insert,
11299        });
11300    });
11301
11302    let mut cx = EditorLspTestContext::new_rust(
11303        lsp::ServerCapabilities {
11304            completion_provider: Some(lsp::CompletionOptions {
11305                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11306                ..lsp::CompletionOptions::default()
11307            }),
11308            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11309            ..lsp::ServerCapabilities::default()
11310        },
11311        cx,
11312    )
11313    .await;
11314
11315    let _completion_requests_handler =
11316        cx.lsp
11317            .server
11318            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11319                panic!("LSP completions should not be queried when dealing with word completions")
11320            });
11321
11322    cx.set_state(indoc! {"ˇ
11323        first
11324        last
11325        second
11326    "});
11327    cx.update_editor(|editor, window, cx| {
11328        editor.show_word_completions(&ShowWordCompletions, window, cx);
11329    });
11330    cx.executor().run_until_parked();
11331    cx.condition(|editor, _| editor.context_menu_visible())
11332        .await;
11333    cx.update_editor(|editor, _, _| {
11334        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11335        {
11336            assert_eq!(
11337                completion_menu_entries(&menu),
11338                &["first", "last", "second"],
11339                "`ShowWordCompletions` action should show word completions"
11340            );
11341        } else {
11342            panic!("expected completion menu to be open");
11343        }
11344    });
11345
11346    cx.simulate_keystroke("l");
11347    cx.executor().run_until_parked();
11348    cx.condition(|editor, _| editor.context_menu_visible())
11349        .await;
11350    cx.update_editor(|editor, _, _| {
11351        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11352        {
11353            assert_eq!(
11354                completion_menu_entries(&menu),
11355                &["last"],
11356                "After showing word completions, further editing should filter them and not query the LSP"
11357            );
11358        } else {
11359            panic!("expected completion menu to be open");
11360        }
11361    });
11362}
11363
11364#[gpui::test]
11365async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11366    init_test(cx, |language_settings| {
11367        language_settings.defaults.completions = Some(CompletionSettings {
11368            words: WordsCompletionMode::Fallback,
11369            lsp: false,
11370            lsp_fetch_timeout_ms: 0,
11371            lsp_insert_mode: LspInsertMode::Insert,
11372        });
11373    });
11374
11375    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11376
11377    cx.set_state(indoc! {"ˇ
11378        0_usize
11379        let
11380        33
11381        4.5f32
11382    "});
11383    cx.update_editor(|editor, window, cx| {
11384        editor.show_completions(&ShowCompletions::default(), window, cx);
11385    });
11386    cx.executor().run_until_parked();
11387    cx.condition(|editor, _| editor.context_menu_visible())
11388        .await;
11389    cx.update_editor(|editor, window, cx| {
11390        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11391        {
11392            assert_eq!(
11393                completion_menu_entries(&menu),
11394                &["let"],
11395                "With no digits in the completion query, no digits should be in the word completions"
11396            );
11397        } else {
11398            panic!("expected completion menu to be open");
11399        }
11400        editor.cancel(&Cancel, window, cx);
11401    });
11402
11403    cx.set_state(indoc! {"11404        0_usize
11405        let
11406        3
11407        33.35f32
11408    "});
11409    cx.update_editor(|editor, window, cx| {
11410        editor.show_completions(&ShowCompletions::default(), window, cx);
11411    });
11412    cx.executor().run_until_parked();
11413    cx.condition(|editor, _| editor.context_menu_visible())
11414        .await;
11415    cx.update_editor(|editor, _, _| {
11416        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11417        {
11418            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11419                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11420        } else {
11421            panic!("expected completion menu to be open");
11422        }
11423    });
11424}
11425
11426fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11427    let position = || lsp::Position {
11428        line: params.text_document_position.position.line,
11429        character: params.text_document_position.position.character,
11430    };
11431    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11432        range: lsp::Range {
11433            start: position(),
11434            end: position(),
11435        },
11436        new_text: text.to_string(),
11437    }))
11438}
11439
11440#[gpui::test]
11441async fn test_multiline_completion(cx: &mut TestAppContext) {
11442    init_test(cx, |_| {});
11443
11444    let fs = FakeFs::new(cx.executor());
11445    fs.insert_tree(
11446        path!("/a"),
11447        json!({
11448            "main.ts": "a",
11449        }),
11450    )
11451    .await;
11452
11453    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11454    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11455    let typescript_language = Arc::new(Language::new(
11456        LanguageConfig {
11457            name: "TypeScript".into(),
11458            matcher: LanguageMatcher {
11459                path_suffixes: vec!["ts".to_string()],
11460                ..LanguageMatcher::default()
11461            },
11462            line_comments: vec!["// ".into()],
11463            ..LanguageConfig::default()
11464        },
11465        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11466    ));
11467    language_registry.add(typescript_language.clone());
11468    let mut fake_servers = language_registry.register_fake_lsp(
11469        "TypeScript",
11470        FakeLspAdapter {
11471            capabilities: lsp::ServerCapabilities {
11472                completion_provider: Some(lsp::CompletionOptions {
11473                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11474                    ..lsp::CompletionOptions::default()
11475                }),
11476                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11477                ..lsp::ServerCapabilities::default()
11478            },
11479            // Emulate vtsls label generation
11480            label_for_completion: Some(Box::new(|item, _| {
11481                let text = if let Some(description) = item
11482                    .label_details
11483                    .as_ref()
11484                    .and_then(|label_details| label_details.description.as_ref())
11485                {
11486                    format!("{} {}", item.label, description)
11487                } else if let Some(detail) = &item.detail {
11488                    format!("{} {}", item.label, detail)
11489                } else {
11490                    item.label.clone()
11491                };
11492                let len = text.len();
11493                Some(language::CodeLabel {
11494                    text,
11495                    runs: Vec::new(),
11496                    filter_range: 0..len,
11497                })
11498            })),
11499            ..FakeLspAdapter::default()
11500        },
11501    );
11502    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11503    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11504    let worktree_id = workspace
11505        .update(cx, |workspace, _window, cx| {
11506            workspace.project().update(cx, |project, cx| {
11507                project.worktrees(cx).next().unwrap().read(cx).id()
11508            })
11509        })
11510        .unwrap();
11511    let _buffer = project
11512        .update(cx, |project, cx| {
11513            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11514        })
11515        .await
11516        .unwrap();
11517    let editor = workspace
11518        .update(cx, |workspace, window, cx| {
11519            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11520        })
11521        .unwrap()
11522        .await
11523        .unwrap()
11524        .downcast::<Editor>()
11525        .unwrap();
11526    let fake_server = fake_servers.next().await.unwrap();
11527
11528    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11529    let multiline_label_2 = "a\nb\nc\n";
11530    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11531    let multiline_description = "d\ne\nf\n";
11532    let multiline_detail_2 = "g\nh\ni\n";
11533
11534    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11535        move |params, _| async move {
11536            Ok(Some(lsp::CompletionResponse::Array(vec![
11537                lsp::CompletionItem {
11538                    label: multiline_label.to_string(),
11539                    text_edit: gen_text_edit(&params, "new_text_1"),
11540                    ..lsp::CompletionItem::default()
11541                },
11542                lsp::CompletionItem {
11543                    label: "single line label 1".to_string(),
11544                    detail: Some(multiline_detail.to_string()),
11545                    text_edit: gen_text_edit(&params, "new_text_2"),
11546                    ..lsp::CompletionItem::default()
11547                },
11548                lsp::CompletionItem {
11549                    label: "single line label 2".to_string(),
11550                    label_details: Some(lsp::CompletionItemLabelDetails {
11551                        description: Some(multiline_description.to_string()),
11552                        detail: None,
11553                    }),
11554                    text_edit: gen_text_edit(&params, "new_text_2"),
11555                    ..lsp::CompletionItem::default()
11556                },
11557                lsp::CompletionItem {
11558                    label: multiline_label_2.to_string(),
11559                    detail: Some(multiline_detail_2.to_string()),
11560                    text_edit: gen_text_edit(&params, "new_text_3"),
11561                    ..lsp::CompletionItem::default()
11562                },
11563                lsp::CompletionItem {
11564                    label: "Label with many     spaces and \t but without newlines".to_string(),
11565                    detail: Some(
11566                        "Details with many     spaces and \t but without newlines".to_string(),
11567                    ),
11568                    text_edit: gen_text_edit(&params, "new_text_4"),
11569                    ..lsp::CompletionItem::default()
11570                },
11571            ])))
11572        },
11573    );
11574
11575    editor.update_in(cx, |editor, window, cx| {
11576        cx.focus_self(window);
11577        editor.move_to_end(&MoveToEnd, window, cx);
11578        editor.handle_input(".", window, cx);
11579    });
11580    cx.run_until_parked();
11581    completion_handle.next().await.unwrap();
11582
11583    editor.update(cx, |editor, _| {
11584        assert!(editor.context_menu_visible());
11585        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11586        {
11587            let completion_labels = menu
11588                .completions
11589                .borrow()
11590                .iter()
11591                .map(|c| c.label.text.clone())
11592                .collect::<Vec<_>>();
11593            assert_eq!(
11594                completion_labels,
11595                &[
11596                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11597                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11598                    "single line label 2 d e f ",
11599                    "a b c g h i ",
11600                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11601                ],
11602                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11603            );
11604
11605            for completion in menu
11606                .completions
11607                .borrow()
11608                .iter() {
11609                    assert_eq!(
11610                        completion.label.filter_range,
11611                        0..completion.label.text.len(),
11612                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11613                    );
11614                }
11615        } else {
11616            panic!("expected completion menu to be open");
11617        }
11618    });
11619}
11620
11621#[gpui::test]
11622async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11623    init_test(cx, |_| {});
11624    let mut cx = EditorLspTestContext::new_rust(
11625        lsp::ServerCapabilities {
11626            completion_provider: Some(lsp::CompletionOptions {
11627                trigger_characters: Some(vec![".".to_string()]),
11628                ..Default::default()
11629            }),
11630            ..Default::default()
11631        },
11632        cx,
11633    )
11634    .await;
11635    cx.lsp
11636        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11637            Ok(Some(lsp::CompletionResponse::Array(vec![
11638                lsp::CompletionItem {
11639                    label: "first".into(),
11640                    ..Default::default()
11641                },
11642                lsp::CompletionItem {
11643                    label: "last".into(),
11644                    ..Default::default()
11645                },
11646            ])))
11647        });
11648    cx.set_state("variableˇ");
11649    cx.simulate_keystroke(".");
11650    cx.executor().run_until_parked();
11651
11652    cx.update_editor(|editor, _, _| {
11653        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11654        {
11655            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11656        } else {
11657            panic!("expected completion menu to be open");
11658        }
11659    });
11660
11661    cx.update_editor(|editor, window, cx| {
11662        editor.move_page_down(&MovePageDown::default(), window, cx);
11663        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11664        {
11665            assert!(
11666                menu.selected_item == 1,
11667                "expected PageDown to select the last item from the context menu"
11668            );
11669        } else {
11670            panic!("expected completion menu to stay open after PageDown");
11671        }
11672    });
11673
11674    cx.update_editor(|editor, window, cx| {
11675        editor.move_page_up(&MovePageUp::default(), window, cx);
11676        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11677        {
11678            assert!(
11679                menu.selected_item == 0,
11680                "expected PageUp to select the first item from the context menu"
11681            );
11682        } else {
11683            panic!("expected completion menu to stay open after PageUp");
11684        }
11685    });
11686}
11687
11688#[gpui::test]
11689async fn test_as_is_completions(cx: &mut TestAppContext) {
11690    init_test(cx, |_| {});
11691    let mut cx = EditorLspTestContext::new_rust(
11692        lsp::ServerCapabilities {
11693            completion_provider: Some(lsp::CompletionOptions {
11694                ..Default::default()
11695            }),
11696            ..Default::default()
11697        },
11698        cx,
11699    )
11700    .await;
11701    cx.lsp
11702        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11703            Ok(Some(lsp::CompletionResponse::Array(vec![
11704                lsp::CompletionItem {
11705                    label: "unsafe".into(),
11706                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11707                        range: lsp::Range {
11708                            start: lsp::Position {
11709                                line: 1,
11710                                character: 2,
11711                            },
11712                            end: lsp::Position {
11713                                line: 1,
11714                                character: 3,
11715                            },
11716                        },
11717                        new_text: "unsafe".to_string(),
11718                    })),
11719                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11720                    ..Default::default()
11721                },
11722            ])))
11723        });
11724    cx.set_state("fn a() {}\n");
11725    cx.executor().run_until_parked();
11726    cx.update_editor(|editor, window, cx| {
11727        editor.show_completions(
11728            &ShowCompletions {
11729                trigger: Some("\n".into()),
11730            },
11731            window,
11732            cx,
11733        );
11734    });
11735    cx.executor().run_until_parked();
11736
11737    cx.update_editor(|editor, window, cx| {
11738        editor.confirm_completion(&Default::default(), window, cx)
11739    });
11740    cx.executor().run_until_parked();
11741    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11742}
11743
11744#[gpui::test]
11745async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11746    init_test(cx, |_| {});
11747
11748    let mut cx = EditorLspTestContext::new_rust(
11749        lsp::ServerCapabilities {
11750            completion_provider: Some(lsp::CompletionOptions {
11751                trigger_characters: Some(vec![".".to_string()]),
11752                resolve_provider: Some(true),
11753                ..Default::default()
11754            }),
11755            ..Default::default()
11756        },
11757        cx,
11758    )
11759    .await;
11760
11761    cx.set_state("fn main() { let a = 2ˇ; }");
11762    cx.simulate_keystroke(".");
11763    let completion_item = lsp::CompletionItem {
11764        label: "Some".into(),
11765        kind: Some(lsp::CompletionItemKind::SNIPPET),
11766        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11767        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11768            kind: lsp::MarkupKind::Markdown,
11769            value: "```rust\nSome(2)\n```".to_string(),
11770        })),
11771        deprecated: Some(false),
11772        sort_text: Some("Some".to_string()),
11773        filter_text: Some("Some".to_string()),
11774        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11775        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11776            range: lsp::Range {
11777                start: lsp::Position {
11778                    line: 0,
11779                    character: 22,
11780                },
11781                end: lsp::Position {
11782                    line: 0,
11783                    character: 22,
11784                },
11785            },
11786            new_text: "Some(2)".to_string(),
11787        })),
11788        additional_text_edits: Some(vec![lsp::TextEdit {
11789            range: lsp::Range {
11790                start: lsp::Position {
11791                    line: 0,
11792                    character: 20,
11793                },
11794                end: lsp::Position {
11795                    line: 0,
11796                    character: 22,
11797                },
11798            },
11799            new_text: "".to_string(),
11800        }]),
11801        ..Default::default()
11802    };
11803
11804    let closure_completion_item = completion_item.clone();
11805    let counter = Arc::new(AtomicUsize::new(0));
11806    let counter_clone = counter.clone();
11807    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
11808        let task_completion_item = closure_completion_item.clone();
11809        counter_clone.fetch_add(1, atomic::Ordering::Release);
11810        async move {
11811            Ok(Some(lsp::CompletionResponse::Array(vec![
11812                task_completion_item,
11813            ])))
11814        }
11815    });
11816
11817    cx.condition(|editor, _| editor.context_menu_visible())
11818        .await;
11819    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
11820    assert!(request.next().await.is_some());
11821    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11822
11823    cx.simulate_keystrokes("S o m");
11824    cx.condition(|editor, _| editor.context_menu_visible())
11825        .await;
11826    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
11827    assert!(request.next().await.is_some());
11828    assert!(request.next().await.is_some());
11829    assert!(request.next().await.is_some());
11830    request.close();
11831    assert!(request.next().await.is_none());
11832    assert_eq!(
11833        counter.load(atomic::Ordering::Acquire),
11834        4,
11835        "With the completions menu open, only one LSP request should happen per input"
11836    );
11837}
11838
11839#[gpui::test]
11840async fn test_toggle_comment(cx: &mut TestAppContext) {
11841    init_test(cx, |_| {});
11842    let mut cx = EditorTestContext::new(cx).await;
11843    let language = Arc::new(Language::new(
11844        LanguageConfig {
11845            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11846            ..Default::default()
11847        },
11848        Some(tree_sitter_rust::LANGUAGE.into()),
11849    ));
11850    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11851
11852    // If multiple selections intersect a line, the line is only toggled once.
11853    cx.set_state(indoc! {"
11854        fn a() {
11855            «//b();
11856            ˇ»// «c();
11857            //ˇ»  d();
11858        }
11859    "});
11860
11861    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11862
11863    cx.assert_editor_state(indoc! {"
11864        fn a() {
11865            «b();
11866            c();
11867            ˇ» d();
11868        }
11869    "});
11870
11871    // The comment prefix is inserted at the same column for every line in a
11872    // selection.
11873    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11874
11875    cx.assert_editor_state(indoc! {"
11876        fn a() {
11877            // «b();
11878            // c();
11879            ˇ»//  d();
11880        }
11881    "});
11882
11883    // If a selection ends at the beginning of a line, that line is not toggled.
11884    cx.set_selections_state(indoc! {"
11885        fn a() {
11886            // b();
11887            «// c();
11888        ˇ»    //  d();
11889        }
11890    "});
11891
11892    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11893
11894    cx.assert_editor_state(indoc! {"
11895        fn a() {
11896            // b();
11897            «c();
11898        ˇ»    //  d();
11899        }
11900    "});
11901
11902    // If a selection span a single line and is empty, the line is toggled.
11903    cx.set_state(indoc! {"
11904        fn a() {
11905            a();
11906            b();
11907        ˇ
11908        }
11909    "});
11910
11911    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11912
11913    cx.assert_editor_state(indoc! {"
11914        fn a() {
11915            a();
11916            b();
11917        //•ˇ
11918        }
11919    "});
11920
11921    // If a selection span multiple lines, empty lines are not toggled.
11922    cx.set_state(indoc! {"
11923        fn a() {
11924            «a();
11925
11926            c();ˇ»
11927        }
11928    "});
11929
11930    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11931
11932    cx.assert_editor_state(indoc! {"
11933        fn a() {
11934            // «a();
11935
11936            // c();ˇ»
11937        }
11938    "});
11939
11940    // If a selection includes multiple comment prefixes, all lines are uncommented.
11941    cx.set_state(indoc! {"
11942        fn a() {
11943            «// a();
11944            /// b();
11945            //! c();ˇ»
11946        }
11947    "});
11948
11949    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
11950
11951    cx.assert_editor_state(indoc! {"
11952        fn a() {
11953            «a();
11954            b();
11955            c();ˇ»
11956        }
11957    "});
11958}
11959
11960#[gpui::test]
11961async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
11962    init_test(cx, |_| {});
11963    let mut cx = EditorTestContext::new(cx).await;
11964    let language = Arc::new(Language::new(
11965        LanguageConfig {
11966            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
11967            ..Default::default()
11968        },
11969        Some(tree_sitter_rust::LANGUAGE.into()),
11970    ));
11971    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11972
11973    let toggle_comments = &ToggleComments {
11974        advance_downwards: false,
11975        ignore_indent: true,
11976    };
11977
11978    // If multiple selections intersect a line, the line is only toggled once.
11979    cx.set_state(indoc! {"
11980        fn a() {
11981        //    «b();
11982        //    c();
11983        //    ˇ» d();
11984        }
11985    "});
11986
11987    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11988
11989    cx.assert_editor_state(indoc! {"
11990        fn a() {
11991            «b();
11992            c();
11993            ˇ» d();
11994        }
11995    "});
11996
11997    // The comment prefix is inserted at the beginning of each line
11998    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
11999
12000    cx.assert_editor_state(indoc! {"
12001        fn a() {
12002        //    «b();
12003        //    c();
12004        //    ˇ» d();
12005        }
12006    "});
12007
12008    // If a selection ends at the beginning of a line, that line is not toggled.
12009    cx.set_selections_state(indoc! {"
12010        fn a() {
12011        //    b();
12012        //    «c();
12013        ˇ»//     d();
12014        }
12015    "});
12016
12017    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12018
12019    cx.assert_editor_state(indoc! {"
12020        fn a() {
12021        //    b();
12022            «c();
12023        ˇ»//     d();
12024        }
12025    "});
12026
12027    // If a selection span a single line and is empty, the line is toggled.
12028    cx.set_state(indoc! {"
12029        fn a() {
12030            a();
12031            b();
12032        ˇ
12033        }
12034    "});
12035
12036    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12037
12038    cx.assert_editor_state(indoc! {"
12039        fn a() {
12040            a();
12041            b();
12042        //ˇ
12043        }
12044    "});
12045
12046    // If a selection span multiple lines, empty lines are not toggled.
12047    cx.set_state(indoc! {"
12048        fn a() {
12049            «a();
12050
12051            c();ˇ»
12052        }
12053    "});
12054
12055    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12056
12057    cx.assert_editor_state(indoc! {"
12058        fn a() {
12059        //    «a();
12060
12061        //    c();ˇ»
12062        }
12063    "});
12064
12065    // If a selection includes multiple comment prefixes, all lines are uncommented.
12066    cx.set_state(indoc! {"
12067        fn a() {
12068        //    «a();
12069        ///    b();
12070        //!    c();ˇ»
12071        }
12072    "});
12073
12074    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12075
12076    cx.assert_editor_state(indoc! {"
12077        fn a() {
12078            «a();
12079            b();
12080            c();ˇ»
12081        }
12082    "});
12083}
12084
12085#[gpui::test]
12086async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12087    init_test(cx, |_| {});
12088
12089    let language = Arc::new(Language::new(
12090        LanguageConfig {
12091            line_comments: vec!["// ".into()],
12092            ..Default::default()
12093        },
12094        Some(tree_sitter_rust::LANGUAGE.into()),
12095    ));
12096
12097    let mut cx = EditorTestContext::new(cx).await;
12098
12099    cx.language_registry().add(language.clone());
12100    cx.update_buffer(|buffer, cx| {
12101        buffer.set_language(Some(language), cx);
12102    });
12103
12104    let toggle_comments = &ToggleComments {
12105        advance_downwards: true,
12106        ignore_indent: false,
12107    };
12108
12109    // Single cursor on one line -> advance
12110    // Cursor moves horizontally 3 characters as well on non-blank line
12111    cx.set_state(indoc!(
12112        "fn a() {
12113             ˇdog();
12114             cat();
12115        }"
12116    ));
12117    cx.update_editor(|editor, window, cx| {
12118        editor.toggle_comments(toggle_comments, window, cx);
12119    });
12120    cx.assert_editor_state(indoc!(
12121        "fn a() {
12122             // dog();
12123             catˇ();
12124        }"
12125    ));
12126
12127    // Single selection on one line -> don't advance
12128    cx.set_state(indoc!(
12129        "fn a() {
12130             «dog()ˇ»;
12131             cat();
12132        }"
12133    ));
12134    cx.update_editor(|editor, window, cx| {
12135        editor.toggle_comments(toggle_comments, window, cx);
12136    });
12137    cx.assert_editor_state(indoc!(
12138        "fn a() {
12139             // «dog()ˇ»;
12140             cat();
12141        }"
12142    ));
12143
12144    // Multiple cursors on one line -> advance
12145    cx.set_state(indoc!(
12146        "fn a() {
12147             ˇdˇog();
12148             cat();
12149        }"
12150    ));
12151    cx.update_editor(|editor, window, cx| {
12152        editor.toggle_comments(toggle_comments, window, cx);
12153    });
12154    cx.assert_editor_state(indoc!(
12155        "fn a() {
12156             // dog();
12157             catˇ(ˇ);
12158        }"
12159    ));
12160
12161    // Multiple cursors on one line, with selection -> don't advance
12162    cx.set_state(indoc!(
12163        "fn a() {
12164             ˇdˇog«()ˇ»;
12165             cat();
12166        }"
12167    ));
12168    cx.update_editor(|editor, window, cx| {
12169        editor.toggle_comments(toggle_comments, window, cx);
12170    });
12171    cx.assert_editor_state(indoc!(
12172        "fn a() {
12173             // ˇdˇog«()ˇ»;
12174             cat();
12175        }"
12176    ));
12177
12178    // Single cursor on one line -> advance
12179    // Cursor moves to column 0 on blank line
12180    cx.set_state(indoc!(
12181        "fn a() {
12182             ˇdog();
12183
12184             cat();
12185        }"
12186    ));
12187    cx.update_editor(|editor, window, cx| {
12188        editor.toggle_comments(toggle_comments, window, cx);
12189    });
12190    cx.assert_editor_state(indoc!(
12191        "fn a() {
12192             // dog();
12193        ˇ
12194             cat();
12195        }"
12196    ));
12197
12198    // Single cursor on one line -> advance
12199    // Cursor starts and ends at column 0
12200    cx.set_state(indoc!(
12201        "fn a() {
12202         ˇ    dog();
12203             cat();
12204        }"
12205    ));
12206    cx.update_editor(|editor, window, cx| {
12207        editor.toggle_comments(toggle_comments, window, cx);
12208    });
12209    cx.assert_editor_state(indoc!(
12210        "fn a() {
12211             // dog();
12212         ˇ    cat();
12213        }"
12214    ));
12215}
12216
12217#[gpui::test]
12218async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12219    init_test(cx, |_| {});
12220
12221    let mut cx = EditorTestContext::new(cx).await;
12222
12223    let html_language = Arc::new(
12224        Language::new(
12225            LanguageConfig {
12226                name: "HTML".into(),
12227                block_comment: Some(("<!-- ".into(), " -->".into())),
12228                ..Default::default()
12229            },
12230            Some(tree_sitter_html::LANGUAGE.into()),
12231        )
12232        .with_injection_query(
12233            r#"
12234            (script_element
12235                (raw_text) @injection.content
12236                (#set! injection.language "javascript"))
12237            "#,
12238        )
12239        .unwrap(),
12240    );
12241
12242    let javascript_language = Arc::new(Language::new(
12243        LanguageConfig {
12244            name: "JavaScript".into(),
12245            line_comments: vec!["// ".into()],
12246            ..Default::default()
12247        },
12248        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12249    ));
12250
12251    cx.language_registry().add(html_language.clone());
12252    cx.language_registry().add(javascript_language.clone());
12253    cx.update_buffer(|buffer, cx| {
12254        buffer.set_language(Some(html_language), cx);
12255    });
12256
12257    // Toggle comments for empty selections
12258    cx.set_state(
12259        &r#"
12260            <p>A</p>ˇ
12261            <p>B</p>ˇ
12262            <p>C</p>ˇ
12263        "#
12264        .unindent(),
12265    );
12266    cx.update_editor(|editor, window, cx| {
12267        editor.toggle_comments(&ToggleComments::default(), window, cx)
12268    });
12269    cx.assert_editor_state(
12270        &r#"
12271            <!-- <p>A</p>ˇ -->
12272            <!-- <p>B</p>ˇ -->
12273            <!-- <p>C</p>ˇ -->
12274        "#
12275        .unindent(),
12276    );
12277    cx.update_editor(|editor, window, cx| {
12278        editor.toggle_comments(&ToggleComments::default(), window, cx)
12279    });
12280    cx.assert_editor_state(
12281        &r#"
12282            <p>A</p>ˇ
12283            <p>B</p>ˇ
12284            <p>C</p>ˇ
12285        "#
12286        .unindent(),
12287    );
12288
12289    // Toggle comments for mixture of empty and non-empty selections, where
12290    // multiple selections occupy a given line.
12291    cx.set_state(
12292        &r#"
12293            <p>A«</p>
12294            <p>ˇ»B</p>ˇ
12295            <p>C«</p>
12296            <p>ˇ»D</p>ˇ
12297        "#
12298        .unindent(),
12299    );
12300
12301    cx.update_editor(|editor, window, cx| {
12302        editor.toggle_comments(&ToggleComments::default(), window, cx)
12303    });
12304    cx.assert_editor_state(
12305        &r#"
12306            <!-- <p>A«</p>
12307            <p>ˇ»B</p>ˇ -->
12308            <!-- <p>C«</p>
12309            <p>ˇ»D</p>ˇ -->
12310        "#
12311        .unindent(),
12312    );
12313    cx.update_editor(|editor, window, cx| {
12314        editor.toggle_comments(&ToggleComments::default(), window, cx)
12315    });
12316    cx.assert_editor_state(
12317        &r#"
12318            <p>A«</p>
12319            <p>ˇ»B</p>ˇ
12320            <p>C«</p>
12321            <p>ˇ»D</p>ˇ
12322        "#
12323        .unindent(),
12324    );
12325
12326    // Toggle comments when different languages are active for different
12327    // selections.
12328    cx.set_state(
12329        &r#"
12330            ˇ<script>
12331                ˇvar x = new Y();
12332            ˇ</script>
12333        "#
12334        .unindent(),
12335    );
12336    cx.executor().run_until_parked();
12337    cx.update_editor(|editor, window, cx| {
12338        editor.toggle_comments(&ToggleComments::default(), window, cx)
12339    });
12340    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12341    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12342    cx.assert_editor_state(
12343        &r#"
12344            <!-- ˇ<script> -->
12345                // ˇvar x = new Y();
12346            <!-- ˇ</script> -->
12347        "#
12348        .unindent(),
12349    );
12350}
12351
12352#[gpui::test]
12353fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12354    init_test(cx, |_| {});
12355
12356    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12357    let multibuffer = cx.new(|cx| {
12358        let mut multibuffer = MultiBuffer::new(ReadWrite);
12359        multibuffer.push_excerpts(
12360            buffer.clone(),
12361            [
12362                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12363                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12364            ],
12365            cx,
12366        );
12367        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12368        multibuffer
12369    });
12370
12371    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12372    editor.update_in(cx, |editor, window, cx| {
12373        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12374        editor.change_selections(None, window, cx, |s| {
12375            s.select_ranges([
12376                Point::new(0, 0)..Point::new(0, 0),
12377                Point::new(1, 0)..Point::new(1, 0),
12378            ])
12379        });
12380
12381        editor.handle_input("X", window, cx);
12382        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12383        assert_eq!(
12384            editor.selections.ranges(cx),
12385            [
12386                Point::new(0, 1)..Point::new(0, 1),
12387                Point::new(1, 1)..Point::new(1, 1),
12388            ]
12389        );
12390
12391        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12392        editor.change_selections(None, window, cx, |s| {
12393            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12394        });
12395        editor.backspace(&Default::default(), window, cx);
12396        assert_eq!(editor.text(cx), "Xa\nbbb");
12397        assert_eq!(
12398            editor.selections.ranges(cx),
12399            [Point::new(1, 0)..Point::new(1, 0)]
12400        );
12401
12402        editor.change_selections(None, window, cx, |s| {
12403            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12404        });
12405        editor.backspace(&Default::default(), window, cx);
12406        assert_eq!(editor.text(cx), "X\nbb");
12407        assert_eq!(
12408            editor.selections.ranges(cx),
12409            [Point::new(0, 1)..Point::new(0, 1)]
12410        );
12411    });
12412}
12413
12414#[gpui::test]
12415fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12416    init_test(cx, |_| {});
12417
12418    let markers = vec![('[', ']').into(), ('(', ')').into()];
12419    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12420        indoc! {"
12421            [aaaa
12422            (bbbb]
12423            cccc)",
12424        },
12425        markers.clone(),
12426    );
12427    let excerpt_ranges = markers.into_iter().map(|marker| {
12428        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12429        ExcerptRange::new(context.clone())
12430    });
12431    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12432    let multibuffer = cx.new(|cx| {
12433        let mut multibuffer = MultiBuffer::new(ReadWrite);
12434        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12435        multibuffer
12436    });
12437
12438    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12439    editor.update_in(cx, |editor, window, cx| {
12440        let (expected_text, selection_ranges) = marked_text_ranges(
12441            indoc! {"
12442                aaaa
12443                bˇbbb
12444                bˇbbˇb
12445                cccc"
12446            },
12447            true,
12448        );
12449        assert_eq!(editor.text(cx), expected_text);
12450        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12451
12452        editor.handle_input("X", window, cx);
12453
12454        let (expected_text, expected_selections) = marked_text_ranges(
12455            indoc! {"
12456                aaaa
12457                bXˇbbXb
12458                bXˇbbXˇb
12459                cccc"
12460            },
12461            false,
12462        );
12463        assert_eq!(editor.text(cx), expected_text);
12464        assert_eq!(editor.selections.ranges(cx), expected_selections);
12465
12466        editor.newline(&Newline, window, cx);
12467        let (expected_text, expected_selections) = marked_text_ranges(
12468            indoc! {"
12469                aaaa
12470                bX
12471                ˇbbX
12472                b
12473                bX
12474                ˇbbX
12475                ˇb
12476                cccc"
12477            },
12478            false,
12479        );
12480        assert_eq!(editor.text(cx), expected_text);
12481        assert_eq!(editor.selections.ranges(cx), expected_selections);
12482    });
12483}
12484
12485#[gpui::test]
12486fn test_refresh_selections(cx: &mut TestAppContext) {
12487    init_test(cx, |_| {});
12488
12489    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12490    let mut excerpt1_id = None;
12491    let multibuffer = cx.new(|cx| {
12492        let mut multibuffer = MultiBuffer::new(ReadWrite);
12493        excerpt1_id = multibuffer
12494            .push_excerpts(
12495                buffer.clone(),
12496                [
12497                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12498                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12499                ],
12500                cx,
12501            )
12502            .into_iter()
12503            .next();
12504        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12505        multibuffer
12506    });
12507
12508    let editor = cx.add_window(|window, cx| {
12509        let mut editor = build_editor(multibuffer.clone(), window, cx);
12510        let snapshot = editor.snapshot(window, cx);
12511        editor.change_selections(None, window, cx, |s| {
12512            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12513        });
12514        editor.begin_selection(
12515            Point::new(2, 1).to_display_point(&snapshot),
12516            true,
12517            1,
12518            window,
12519            cx,
12520        );
12521        assert_eq!(
12522            editor.selections.ranges(cx),
12523            [
12524                Point::new(1, 3)..Point::new(1, 3),
12525                Point::new(2, 1)..Point::new(2, 1),
12526            ]
12527        );
12528        editor
12529    });
12530
12531    // Refreshing selections is a no-op when excerpts haven't changed.
12532    _ = editor.update(cx, |editor, window, cx| {
12533        editor.change_selections(None, window, cx, |s| s.refresh());
12534        assert_eq!(
12535            editor.selections.ranges(cx),
12536            [
12537                Point::new(1, 3)..Point::new(1, 3),
12538                Point::new(2, 1)..Point::new(2, 1),
12539            ]
12540        );
12541    });
12542
12543    multibuffer.update(cx, |multibuffer, cx| {
12544        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12545    });
12546    _ = editor.update(cx, |editor, window, cx| {
12547        // Removing an excerpt causes the first selection to become degenerate.
12548        assert_eq!(
12549            editor.selections.ranges(cx),
12550            [
12551                Point::new(0, 0)..Point::new(0, 0),
12552                Point::new(0, 1)..Point::new(0, 1)
12553            ]
12554        );
12555
12556        // Refreshing selections will relocate the first selection to the original buffer
12557        // location.
12558        editor.change_selections(None, window, cx, |s| s.refresh());
12559        assert_eq!(
12560            editor.selections.ranges(cx),
12561            [
12562                Point::new(0, 1)..Point::new(0, 1),
12563                Point::new(0, 3)..Point::new(0, 3)
12564            ]
12565        );
12566        assert!(editor.selections.pending_anchor().is_some());
12567    });
12568}
12569
12570#[gpui::test]
12571fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12572    init_test(cx, |_| {});
12573
12574    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12575    let mut excerpt1_id = None;
12576    let multibuffer = cx.new(|cx| {
12577        let mut multibuffer = MultiBuffer::new(ReadWrite);
12578        excerpt1_id = multibuffer
12579            .push_excerpts(
12580                buffer.clone(),
12581                [
12582                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12583                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12584                ],
12585                cx,
12586            )
12587            .into_iter()
12588            .next();
12589        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12590        multibuffer
12591    });
12592
12593    let editor = cx.add_window(|window, cx| {
12594        let mut editor = build_editor(multibuffer.clone(), window, cx);
12595        let snapshot = editor.snapshot(window, cx);
12596        editor.begin_selection(
12597            Point::new(1, 3).to_display_point(&snapshot),
12598            false,
12599            1,
12600            window,
12601            cx,
12602        );
12603        assert_eq!(
12604            editor.selections.ranges(cx),
12605            [Point::new(1, 3)..Point::new(1, 3)]
12606        );
12607        editor
12608    });
12609
12610    multibuffer.update(cx, |multibuffer, cx| {
12611        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12612    });
12613    _ = editor.update(cx, |editor, window, cx| {
12614        assert_eq!(
12615            editor.selections.ranges(cx),
12616            [Point::new(0, 0)..Point::new(0, 0)]
12617        );
12618
12619        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12620        editor.change_selections(None, window, cx, |s| s.refresh());
12621        assert_eq!(
12622            editor.selections.ranges(cx),
12623            [Point::new(0, 3)..Point::new(0, 3)]
12624        );
12625        assert!(editor.selections.pending_anchor().is_some());
12626    });
12627}
12628
12629#[gpui::test]
12630async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12631    init_test(cx, |_| {});
12632
12633    let language = Arc::new(
12634        Language::new(
12635            LanguageConfig {
12636                brackets: BracketPairConfig {
12637                    pairs: vec![
12638                        BracketPair {
12639                            start: "{".to_string(),
12640                            end: "}".to_string(),
12641                            close: true,
12642                            surround: true,
12643                            newline: true,
12644                        },
12645                        BracketPair {
12646                            start: "/* ".to_string(),
12647                            end: " */".to_string(),
12648                            close: true,
12649                            surround: true,
12650                            newline: true,
12651                        },
12652                    ],
12653                    ..Default::default()
12654                },
12655                ..Default::default()
12656            },
12657            Some(tree_sitter_rust::LANGUAGE.into()),
12658        )
12659        .with_indents_query("")
12660        .unwrap(),
12661    );
12662
12663    let text = concat!(
12664        "{   }\n",     //
12665        "  x\n",       //
12666        "  /*   */\n", //
12667        "x\n",         //
12668        "{{} }\n",     //
12669    );
12670
12671    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12672    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12673    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12674    editor
12675        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12676        .await;
12677
12678    editor.update_in(cx, |editor, window, cx| {
12679        editor.change_selections(None, window, cx, |s| {
12680            s.select_display_ranges([
12681                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12682                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12683                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12684            ])
12685        });
12686        editor.newline(&Newline, window, cx);
12687
12688        assert_eq!(
12689            editor.buffer().read(cx).read(cx).text(),
12690            concat!(
12691                "{ \n",    // Suppress rustfmt
12692                "\n",      //
12693                "}\n",     //
12694                "  x\n",   //
12695                "  /* \n", //
12696                "  \n",    //
12697                "  */\n",  //
12698                "x\n",     //
12699                "{{} \n",  //
12700                "}\n",     //
12701            )
12702        );
12703    });
12704}
12705
12706#[gpui::test]
12707fn test_highlighted_ranges(cx: &mut TestAppContext) {
12708    init_test(cx, |_| {});
12709
12710    let editor = cx.add_window(|window, cx| {
12711        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12712        build_editor(buffer.clone(), window, cx)
12713    });
12714
12715    _ = editor.update(cx, |editor, window, cx| {
12716        struct Type1;
12717        struct Type2;
12718
12719        let buffer = editor.buffer.read(cx).snapshot(cx);
12720
12721        let anchor_range =
12722            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12723
12724        editor.highlight_background::<Type1>(
12725            &[
12726                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12727                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12728                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12729                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12730            ],
12731            |_| Hsla::red(),
12732            cx,
12733        );
12734        editor.highlight_background::<Type2>(
12735            &[
12736                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12737                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12738                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12739                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12740            ],
12741            |_| Hsla::green(),
12742            cx,
12743        );
12744
12745        let snapshot = editor.snapshot(window, cx);
12746        let mut highlighted_ranges = editor.background_highlights_in_range(
12747            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12748            &snapshot,
12749            cx.theme().colors(),
12750        );
12751        // Enforce a consistent ordering based on color without relying on the ordering of the
12752        // highlight's `TypeId` which is non-executor.
12753        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12754        assert_eq!(
12755            highlighted_ranges,
12756            &[
12757                (
12758                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12759                    Hsla::red(),
12760                ),
12761                (
12762                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12763                    Hsla::red(),
12764                ),
12765                (
12766                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12767                    Hsla::green(),
12768                ),
12769                (
12770                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12771                    Hsla::green(),
12772                ),
12773            ]
12774        );
12775        assert_eq!(
12776            editor.background_highlights_in_range(
12777                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12778                &snapshot,
12779                cx.theme().colors(),
12780            ),
12781            &[(
12782                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12783                Hsla::red(),
12784            )]
12785        );
12786    });
12787}
12788
12789#[gpui::test]
12790async fn test_following(cx: &mut TestAppContext) {
12791    init_test(cx, |_| {});
12792
12793    let fs = FakeFs::new(cx.executor());
12794    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12795
12796    let buffer = project.update(cx, |project, cx| {
12797        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12798        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12799    });
12800    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12801    let follower = cx.update(|cx| {
12802        cx.open_window(
12803            WindowOptions {
12804                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
12805                    gpui::Point::new(px(0.), px(0.)),
12806                    gpui::Point::new(px(10.), px(80.)),
12807                ))),
12808                ..Default::default()
12809            },
12810            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
12811        )
12812        .unwrap()
12813    });
12814
12815    let is_still_following = Rc::new(RefCell::new(true));
12816    let follower_edit_event_count = Rc::new(RefCell::new(0));
12817    let pending_update = Rc::new(RefCell::new(None));
12818    let leader_entity = leader.root(cx).unwrap();
12819    let follower_entity = follower.root(cx).unwrap();
12820    _ = follower.update(cx, {
12821        let update = pending_update.clone();
12822        let is_still_following = is_still_following.clone();
12823        let follower_edit_event_count = follower_edit_event_count.clone();
12824        |_, window, cx| {
12825            cx.subscribe_in(
12826                &leader_entity,
12827                window,
12828                move |_, leader, event, window, cx| {
12829                    leader.read(cx).add_event_to_update_proto(
12830                        event,
12831                        &mut update.borrow_mut(),
12832                        window,
12833                        cx,
12834                    );
12835                },
12836            )
12837            .detach();
12838
12839            cx.subscribe_in(
12840                &follower_entity,
12841                window,
12842                move |_, _, event: &EditorEvent, _window, _cx| {
12843                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
12844                        *is_still_following.borrow_mut() = false;
12845                    }
12846
12847                    if let EditorEvent::BufferEdited = event {
12848                        *follower_edit_event_count.borrow_mut() += 1;
12849                    }
12850                },
12851            )
12852            .detach();
12853        }
12854    });
12855
12856    // Update the selections only
12857    _ = leader.update(cx, |leader, window, cx| {
12858        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
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    _ = follower.update(cx, |follower, _, cx| {
12873        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
12874    });
12875    assert!(*is_still_following.borrow());
12876    assert_eq!(*follower_edit_event_count.borrow(), 0);
12877
12878    // Update the scroll position only
12879    _ = leader.update(cx, |leader, window, cx| {
12880        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12881    });
12882    follower
12883        .update(cx, |follower, window, cx| {
12884            follower.apply_update_proto(
12885                &project,
12886                pending_update.borrow_mut().take().unwrap(),
12887                window,
12888                cx,
12889            )
12890        })
12891        .unwrap()
12892        .await
12893        .unwrap();
12894    assert_eq!(
12895        follower
12896            .update(cx, |follower, _, cx| follower.scroll_position(cx))
12897            .unwrap(),
12898        gpui::Point::new(1.5, 3.5)
12899    );
12900    assert!(*is_still_following.borrow());
12901    assert_eq!(*follower_edit_event_count.borrow(), 0);
12902
12903    // Update the selections and scroll position. The follower's scroll position is updated
12904    // via autoscroll, not via the leader's exact scroll position.
12905    _ = leader.update(cx, |leader, window, cx| {
12906        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
12907        leader.request_autoscroll(Autoscroll::newest(), cx);
12908        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
12909    });
12910    follower
12911        .update(cx, |follower, window, cx| {
12912            follower.apply_update_proto(
12913                &project,
12914                pending_update.borrow_mut().take().unwrap(),
12915                window,
12916                cx,
12917            )
12918        })
12919        .unwrap()
12920        .await
12921        .unwrap();
12922    _ = follower.update(cx, |follower, _, cx| {
12923        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
12924        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
12925    });
12926    assert!(*is_still_following.borrow());
12927
12928    // Creating a pending selection that precedes another selection
12929    _ = leader.update(cx, |leader, window, cx| {
12930        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
12931        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
12932    });
12933    follower
12934        .update(cx, |follower, window, cx| {
12935            follower.apply_update_proto(
12936                &project,
12937                pending_update.borrow_mut().take().unwrap(),
12938                window,
12939                cx,
12940            )
12941        })
12942        .unwrap()
12943        .await
12944        .unwrap();
12945    _ = follower.update(cx, |follower, _, cx| {
12946        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
12947    });
12948    assert!(*is_still_following.borrow());
12949
12950    // Extend the pending selection so that it surrounds another selection
12951    _ = leader.update(cx, |leader, window, cx| {
12952        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
12953    });
12954    follower
12955        .update(cx, |follower, window, cx| {
12956            follower.apply_update_proto(
12957                &project,
12958                pending_update.borrow_mut().take().unwrap(),
12959                window,
12960                cx,
12961            )
12962        })
12963        .unwrap()
12964        .await
12965        .unwrap();
12966    _ = follower.update(cx, |follower, _, cx| {
12967        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
12968    });
12969
12970    // Scrolling locally breaks the follow
12971    _ = follower.update(cx, |follower, window, cx| {
12972        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
12973        follower.set_scroll_anchor(
12974            ScrollAnchor {
12975                anchor: top_anchor,
12976                offset: gpui::Point::new(0.0, 0.5),
12977            },
12978            window,
12979            cx,
12980        );
12981    });
12982    assert!(!(*is_still_following.borrow()));
12983}
12984
12985#[gpui::test]
12986async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
12987    init_test(cx, |_| {});
12988
12989    let fs = FakeFs::new(cx.executor());
12990    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12991    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12992    let pane = workspace
12993        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12994        .unwrap();
12995
12996    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12997
12998    let leader = pane.update_in(cx, |_, window, cx| {
12999        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13000        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13001    });
13002
13003    // Start following the editor when it has no excerpts.
13004    let mut state_message =
13005        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13006    let workspace_entity = workspace.root(cx).unwrap();
13007    let follower_1 = cx
13008        .update_window(*workspace.deref(), |_, window, cx| {
13009            Editor::from_state_proto(
13010                workspace_entity,
13011                ViewId {
13012                    creator: CollaboratorId::PeerId(PeerId::default()),
13013                    id: 0,
13014                },
13015                &mut state_message,
13016                window,
13017                cx,
13018            )
13019        })
13020        .unwrap()
13021        .unwrap()
13022        .await
13023        .unwrap();
13024
13025    let update_message = Rc::new(RefCell::new(None));
13026    follower_1.update_in(cx, {
13027        let update = update_message.clone();
13028        |_, window, cx| {
13029            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13030                leader.read(cx).add_event_to_update_proto(
13031                    event,
13032                    &mut update.borrow_mut(),
13033                    window,
13034                    cx,
13035                );
13036            })
13037            .detach();
13038        }
13039    });
13040
13041    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13042        (
13043            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13044            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13045        )
13046    });
13047
13048    // Insert some excerpts.
13049    leader.update(cx, |leader, cx| {
13050        leader.buffer.update(cx, |multibuffer, cx| {
13051            multibuffer.set_excerpts_for_path(
13052                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13053                buffer_1.clone(),
13054                vec![
13055                    Point::row_range(0..3),
13056                    Point::row_range(1..6),
13057                    Point::row_range(12..15),
13058                ],
13059                0,
13060                cx,
13061            );
13062            multibuffer.set_excerpts_for_path(
13063                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13064                buffer_2.clone(),
13065                vec![Point::row_range(0..6), Point::row_range(8..12)],
13066                0,
13067                cx,
13068            );
13069        });
13070    });
13071
13072    // Apply the update of adding the excerpts.
13073    follower_1
13074        .update_in(cx, |follower, window, cx| {
13075            follower.apply_update_proto(
13076                &project,
13077                update_message.borrow().clone().unwrap(),
13078                window,
13079                cx,
13080            )
13081        })
13082        .await
13083        .unwrap();
13084    assert_eq!(
13085        follower_1.update(cx, |editor, cx| editor.text(cx)),
13086        leader.update(cx, |editor, cx| editor.text(cx))
13087    );
13088    update_message.borrow_mut().take();
13089
13090    // Start following separately after it already has excerpts.
13091    let mut state_message =
13092        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13093    let workspace_entity = workspace.root(cx).unwrap();
13094    let follower_2 = cx
13095        .update_window(*workspace.deref(), |_, window, cx| {
13096            Editor::from_state_proto(
13097                workspace_entity,
13098                ViewId {
13099                    creator: CollaboratorId::PeerId(PeerId::default()),
13100                    id: 0,
13101                },
13102                &mut state_message,
13103                window,
13104                cx,
13105            )
13106        })
13107        .unwrap()
13108        .unwrap()
13109        .await
13110        .unwrap();
13111    assert_eq!(
13112        follower_2.update(cx, |editor, cx| editor.text(cx)),
13113        leader.update(cx, |editor, cx| editor.text(cx))
13114    );
13115
13116    // Remove some excerpts.
13117    leader.update(cx, |leader, cx| {
13118        leader.buffer.update(cx, |multibuffer, cx| {
13119            let excerpt_ids = multibuffer.excerpt_ids();
13120            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13121            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13122        });
13123    });
13124
13125    // Apply the update of removing the excerpts.
13126    follower_1
13127        .update_in(cx, |follower, window, cx| {
13128            follower.apply_update_proto(
13129                &project,
13130                update_message.borrow().clone().unwrap(),
13131                window,
13132                cx,
13133            )
13134        })
13135        .await
13136        .unwrap();
13137    follower_2
13138        .update_in(cx, |follower, window, cx| {
13139            follower.apply_update_proto(
13140                &project,
13141                update_message.borrow().clone().unwrap(),
13142                window,
13143                cx,
13144            )
13145        })
13146        .await
13147        .unwrap();
13148    update_message.borrow_mut().take();
13149    assert_eq!(
13150        follower_1.update(cx, |editor, cx| editor.text(cx)),
13151        leader.update(cx, |editor, cx| editor.text(cx))
13152    );
13153}
13154
13155#[gpui::test]
13156async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13157    init_test(cx, |_| {});
13158
13159    let mut cx = EditorTestContext::new(cx).await;
13160    let lsp_store =
13161        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13162
13163    cx.set_state(indoc! {"
13164        ˇfn func(abc def: i32) -> u32 {
13165        }
13166    "});
13167
13168    cx.update(|_, cx| {
13169        lsp_store.update(cx, |lsp_store, cx| {
13170            lsp_store
13171                .update_diagnostics(
13172                    LanguageServerId(0),
13173                    lsp::PublishDiagnosticsParams {
13174                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13175                        version: None,
13176                        diagnostics: vec![
13177                            lsp::Diagnostic {
13178                                range: lsp::Range::new(
13179                                    lsp::Position::new(0, 11),
13180                                    lsp::Position::new(0, 12),
13181                                ),
13182                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13183                                ..Default::default()
13184                            },
13185                            lsp::Diagnostic {
13186                                range: lsp::Range::new(
13187                                    lsp::Position::new(0, 12),
13188                                    lsp::Position::new(0, 15),
13189                                ),
13190                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13191                                ..Default::default()
13192                            },
13193                            lsp::Diagnostic {
13194                                range: lsp::Range::new(
13195                                    lsp::Position::new(0, 25),
13196                                    lsp::Position::new(0, 28),
13197                                ),
13198                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13199                                ..Default::default()
13200                            },
13201                        ],
13202                    },
13203                    &[],
13204                    cx,
13205                )
13206                .unwrap()
13207        });
13208    });
13209
13210    executor.run_until_parked();
13211
13212    cx.update_editor(|editor, window, cx| {
13213        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13214    });
13215
13216    cx.assert_editor_state(indoc! {"
13217        fn func(abc def: i32) -> ˇu32 {
13218        }
13219    "});
13220
13221    cx.update_editor(|editor, window, cx| {
13222        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13223    });
13224
13225    cx.assert_editor_state(indoc! {"
13226        fn func(abc ˇdef: i32) -> u32 {
13227        }
13228    "});
13229
13230    cx.update_editor(|editor, window, cx| {
13231        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13232    });
13233
13234    cx.assert_editor_state(indoc! {"
13235        fn func(abcˇ def: i32) -> u32 {
13236        }
13237    "});
13238
13239    cx.update_editor(|editor, window, cx| {
13240        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13241    });
13242
13243    cx.assert_editor_state(indoc! {"
13244        fn func(abc def: i32) -> ˇu32 {
13245        }
13246    "});
13247}
13248
13249#[gpui::test]
13250async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13251    init_test(cx, |_| {});
13252
13253    let mut cx = EditorTestContext::new(cx).await;
13254
13255    let diff_base = r#"
13256        use some::mod;
13257
13258        const A: u32 = 42;
13259
13260        fn main() {
13261            println!("hello");
13262
13263            println!("world");
13264        }
13265        "#
13266    .unindent();
13267
13268    // Edits are modified, removed, modified, added
13269    cx.set_state(
13270        &r#"
13271        use some::modified;
13272
13273        ˇ
13274        fn main() {
13275            println!("hello there");
13276
13277            println!("around the");
13278            println!("world");
13279        }
13280        "#
13281        .unindent(),
13282    );
13283
13284    cx.set_head_text(&diff_base);
13285    executor.run_until_parked();
13286
13287    cx.update_editor(|editor, window, cx| {
13288        //Wrap around the bottom of the buffer
13289        for _ in 0..3 {
13290            editor.go_to_next_hunk(&GoToHunk, 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        //Wrap around the top of the buffer
13311        for _ in 0..2 {
13312            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13313        }
13314    });
13315
13316    cx.assert_editor_state(
13317        &r#"
13318        use some::modified;
13319
13320
13321        fn main() {
13322        ˇ    println!("hello there");
13323
13324            println!("around the");
13325            println!("world");
13326        }
13327        "#
13328        .unindent(),
13329    );
13330
13331    cx.update_editor(|editor, window, cx| {
13332        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13333    });
13334
13335    cx.assert_editor_state(
13336        &r#"
13337        use some::modified;
13338
13339        ˇ
13340        fn main() {
13341            println!("hello there");
13342
13343            println!("around the");
13344            println!("world");
13345        }
13346        "#
13347        .unindent(),
13348    );
13349
13350    cx.update_editor(|editor, window, cx| {
13351        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13352    });
13353
13354    cx.assert_editor_state(
13355        &r#"
13356        ˇuse some::modified;
13357
13358
13359        fn main() {
13360            println!("hello there");
13361
13362            println!("around the");
13363            println!("world");
13364        }
13365        "#
13366        .unindent(),
13367    );
13368
13369    cx.update_editor(|editor, window, cx| {
13370        for _ in 0..2 {
13371            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13372        }
13373    });
13374
13375    cx.assert_editor_state(
13376        &r#"
13377        use some::modified;
13378
13379
13380        fn main() {
13381        ˇ    println!("hello there");
13382
13383            println!("around the");
13384            println!("world");
13385        }
13386        "#
13387        .unindent(),
13388    );
13389
13390    cx.update_editor(|editor, window, cx| {
13391        editor.fold(&Fold, window, cx);
13392    });
13393
13394    cx.update_editor(|editor, window, cx| {
13395        editor.go_to_next_hunk(&GoToHunk, window, cx);
13396    });
13397
13398    cx.assert_editor_state(
13399        &r#"
13400        ˇuse some::modified;
13401
13402
13403        fn main() {
13404            println!("hello there");
13405
13406            println!("around the");
13407            println!("world");
13408        }
13409        "#
13410        .unindent(),
13411    );
13412}
13413
13414#[test]
13415fn test_split_words() {
13416    fn split(text: &str) -> Vec<&str> {
13417        split_words(text).collect()
13418    }
13419
13420    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13421    assert_eq!(split("hello_world"), &["hello_", "world"]);
13422    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13423    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13424    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13425    assert_eq!(split("helloworld"), &["helloworld"]);
13426
13427    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13428}
13429
13430#[gpui::test]
13431async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13432    init_test(cx, |_| {});
13433
13434    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13435    let mut assert = |before, after| {
13436        let _state_context = cx.set_state(before);
13437        cx.run_until_parked();
13438        cx.update_editor(|editor, window, cx| {
13439            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13440        });
13441        cx.run_until_parked();
13442        cx.assert_editor_state(after);
13443    };
13444
13445    // Outside bracket jumps to outside of matching bracket
13446    assert("console.logˇ(var);", "console.log(var)ˇ;");
13447    assert("console.log(var)ˇ;", "console.logˇ(var);");
13448
13449    // Inside bracket jumps to inside of matching bracket
13450    assert("console.log(ˇvar);", "console.log(varˇ);");
13451    assert("console.log(varˇ);", "console.log(ˇvar);");
13452
13453    // When outside a bracket and inside, favor jumping to the inside bracket
13454    assert(
13455        "console.log('foo', [1, 2, 3]ˇ);",
13456        "console.log(ˇ'foo', [1, 2, 3]);",
13457    );
13458    assert(
13459        "console.log(ˇ'foo', [1, 2, 3]);",
13460        "console.log('foo', [1, 2, 3]ˇ);",
13461    );
13462
13463    // Bias forward if two options are equally likely
13464    assert(
13465        "let result = curried_fun()ˇ();",
13466        "let result = curried_fun()()ˇ;",
13467    );
13468
13469    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13470    assert(
13471        indoc! {"
13472            function test() {
13473                console.log('test')ˇ
13474            }"},
13475        indoc! {"
13476            function test() {
13477                console.logˇ('test')
13478            }"},
13479    );
13480}
13481
13482#[gpui::test]
13483async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13484    init_test(cx, |_| {});
13485
13486    let fs = FakeFs::new(cx.executor());
13487    fs.insert_tree(
13488        path!("/a"),
13489        json!({
13490            "main.rs": "fn main() { let a = 5; }",
13491            "other.rs": "// Test file",
13492        }),
13493    )
13494    .await;
13495    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13496
13497    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13498    language_registry.add(Arc::new(Language::new(
13499        LanguageConfig {
13500            name: "Rust".into(),
13501            matcher: LanguageMatcher {
13502                path_suffixes: vec!["rs".to_string()],
13503                ..Default::default()
13504            },
13505            brackets: BracketPairConfig {
13506                pairs: vec![BracketPair {
13507                    start: "{".to_string(),
13508                    end: "}".to_string(),
13509                    close: true,
13510                    surround: true,
13511                    newline: true,
13512                }],
13513                disabled_scopes_by_bracket_ix: Vec::new(),
13514            },
13515            ..Default::default()
13516        },
13517        Some(tree_sitter_rust::LANGUAGE.into()),
13518    )));
13519    let mut fake_servers = language_registry.register_fake_lsp(
13520        "Rust",
13521        FakeLspAdapter {
13522            capabilities: lsp::ServerCapabilities {
13523                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13524                    first_trigger_character: "{".to_string(),
13525                    more_trigger_character: None,
13526                }),
13527                ..Default::default()
13528            },
13529            ..Default::default()
13530        },
13531    );
13532
13533    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13534
13535    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13536
13537    let worktree_id = workspace
13538        .update(cx, |workspace, _, cx| {
13539            workspace.project().update(cx, |project, cx| {
13540                project.worktrees(cx).next().unwrap().read(cx).id()
13541            })
13542        })
13543        .unwrap();
13544
13545    let buffer = project
13546        .update(cx, |project, cx| {
13547            project.open_local_buffer(path!("/a/main.rs"), cx)
13548        })
13549        .await
13550        .unwrap();
13551    let editor_handle = workspace
13552        .update(cx, |workspace, window, cx| {
13553            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13554        })
13555        .unwrap()
13556        .await
13557        .unwrap()
13558        .downcast::<Editor>()
13559        .unwrap();
13560
13561    cx.executor().start_waiting();
13562    let fake_server = fake_servers.next().await.unwrap();
13563
13564    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13565        |params, _| async move {
13566            assert_eq!(
13567                params.text_document_position.text_document.uri,
13568                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13569            );
13570            assert_eq!(
13571                params.text_document_position.position,
13572                lsp::Position::new(0, 21),
13573            );
13574
13575            Ok(Some(vec![lsp::TextEdit {
13576                new_text: "]".to_string(),
13577                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13578            }]))
13579        },
13580    );
13581
13582    editor_handle.update_in(cx, |editor, window, cx| {
13583        window.focus(&editor.focus_handle(cx));
13584        editor.change_selections(None, window, cx, |s| {
13585            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13586        });
13587        editor.handle_input("{", window, cx);
13588    });
13589
13590    cx.executor().run_until_parked();
13591
13592    buffer.update(cx, |buffer, _| {
13593        assert_eq!(
13594            buffer.text(),
13595            "fn main() { let a = {5}; }",
13596            "No extra braces from on type formatting should appear in the buffer"
13597        )
13598    });
13599}
13600
13601#[gpui::test]
13602async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604
13605    let fs = FakeFs::new(cx.executor());
13606    fs.insert_tree(
13607        path!("/a"),
13608        json!({
13609            "main.rs": "fn main() { let a = 5; }",
13610            "other.rs": "// Test file",
13611        }),
13612    )
13613    .await;
13614
13615    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13616
13617    let server_restarts = Arc::new(AtomicUsize::new(0));
13618    let closure_restarts = Arc::clone(&server_restarts);
13619    let language_server_name = "test language server";
13620    let language_name: LanguageName = "Rust".into();
13621
13622    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13623    language_registry.add(Arc::new(Language::new(
13624        LanguageConfig {
13625            name: language_name.clone(),
13626            matcher: LanguageMatcher {
13627                path_suffixes: vec!["rs".to_string()],
13628                ..Default::default()
13629            },
13630            ..Default::default()
13631        },
13632        Some(tree_sitter_rust::LANGUAGE.into()),
13633    )));
13634    let mut fake_servers = language_registry.register_fake_lsp(
13635        "Rust",
13636        FakeLspAdapter {
13637            name: language_server_name,
13638            initialization_options: Some(json!({
13639                "testOptionValue": true
13640            })),
13641            initializer: Some(Box::new(move |fake_server| {
13642                let task_restarts = Arc::clone(&closure_restarts);
13643                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13644                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13645                    futures::future::ready(Ok(()))
13646                });
13647            })),
13648            ..Default::default()
13649        },
13650    );
13651
13652    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13653    let _buffer = project
13654        .update(cx, |project, cx| {
13655            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13656        })
13657        .await
13658        .unwrap();
13659    let _fake_server = fake_servers.next().await.unwrap();
13660    update_test_language_settings(cx, |language_settings| {
13661        language_settings.languages.insert(
13662            language_name.clone(),
13663            LanguageSettingsContent {
13664                tab_size: NonZeroU32::new(8),
13665                ..Default::default()
13666            },
13667        );
13668    });
13669    cx.executor().run_until_parked();
13670    assert_eq!(
13671        server_restarts.load(atomic::Ordering::Acquire),
13672        0,
13673        "Should not restart LSP server on an unrelated change"
13674    );
13675
13676    update_test_project_settings(cx, |project_settings| {
13677        project_settings.lsp.insert(
13678            "Some other server name".into(),
13679            LspSettings {
13680                binary: None,
13681                settings: None,
13682                initialization_options: Some(json!({
13683                    "some other init value": false
13684                })),
13685                enable_lsp_tasks: false,
13686            },
13687        );
13688    });
13689    cx.executor().run_until_parked();
13690    assert_eq!(
13691        server_restarts.load(atomic::Ordering::Acquire),
13692        0,
13693        "Should not restart LSP server on an unrelated LSP settings change"
13694    );
13695
13696    update_test_project_settings(cx, |project_settings| {
13697        project_settings.lsp.insert(
13698            language_server_name.into(),
13699            LspSettings {
13700                binary: None,
13701                settings: None,
13702                initialization_options: Some(json!({
13703                    "anotherInitValue": false
13704                })),
13705                enable_lsp_tasks: false,
13706            },
13707        );
13708    });
13709    cx.executor().run_until_parked();
13710    assert_eq!(
13711        server_restarts.load(atomic::Ordering::Acquire),
13712        1,
13713        "Should restart LSP server on a related LSP settings change"
13714    );
13715
13716    update_test_project_settings(cx, |project_settings| {
13717        project_settings.lsp.insert(
13718            language_server_name.into(),
13719            LspSettings {
13720                binary: None,
13721                settings: None,
13722                initialization_options: Some(json!({
13723                    "anotherInitValue": false
13724                })),
13725                enable_lsp_tasks: false,
13726            },
13727        );
13728    });
13729    cx.executor().run_until_parked();
13730    assert_eq!(
13731        server_restarts.load(atomic::Ordering::Acquire),
13732        1,
13733        "Should not restart LSP server on a related LSP settings change that is the same"
13734    );
13735
13736    update_test_project_settings(cx, |project_settings| {
13737        project_settings.lsp.insert(
13738            language_server_name.into(),
13739            LspSettings {
13740                binary: None,
13741                settings: None,
13742                initialization_options: None,
13743                enable_lsp_tasks: false,
13744            },
13745        );
13746    });
13747    cx.executor().run_until_parked();
13748    assert_eq!(
13749        server_restarts.load(atomic::Ordering::Acquire),
13750        2,
13751        "Should restart LSP server on another related LSP settings change"
13752    );
13753}
13754
13755#[gpui::test]
13756async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13757    init_test(cx, |_| {});
13758
13759    let mut cx = EditorLspTestContext::new_rust(
13760        lsp::ServerCapabilities {
13761            completion_provider: Some(lsp::CompletionOptions {
13762                trigger_characters: Some(vec![".".to_string()]),
13763                resolve_provider: Some(true),
13764                ..Default::default()
13765            }),
13766            ..Default::default()
13767        },
13768        cx,
13769    )
13770    .await;
13771
13772    cx.set_state("fn main() { let a = 2ˇ; }");
13773    cx.simulate_keystroke(".");
13774    let completion_item = lsp::CompletionItem {
13775        label: "some".into(),
13776        kind: Some(lsp::CompletionItemKind::SNIPPET),
13777        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13778        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13779            kind: lsp::MarkupKind::Markdown,
13780            value: "```rust\nSome(2)\n```".to_string(),
13781        })),
13782        deprecated: Some(false),
13783        sort_text: Some("fffffff2".to_string()),
13784        filter_text: Some("some".to_string()),
13785        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13786        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13787            range: lsp::Range {
13788                start: lsp::Position {
13789                    line: 0,
13790                    character: 22,
13791                },
13792                end: lsp::Position {
13793                    line: 0,
13794                    character: 22,
13795                },
13796            },
13797            new_text: "Some(2)".to_string(),
13798        })),
13799        additional_text_edits: Some(vec![lsp::TextEdit {
13800            range: lsp::Range {
13801                start: lsp::Position {
13802                    line: 0,
13803                    character: 20,
13804                },
13805                end: lsp::Position {
13806                    line: 0,
13807                    character: 22,
13808                },
13809            },
13810            new_text: "".to_string(),
13811        }]),
13812        ..Default::default()
13813    };
13814
13815    let closure_completion_item = completion_item.clone();
13816    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13817        let task_completion_item = closure_completion_item.clone();
13818        async move {
13819            Ok(Some(lsp::CompletionResponse::Array(vec![
13820                task_completion_item,
13821            ])))
13822        }
13823    });
13824
13825    request.next().await;
13826
13827    cx.condition(|editor, _| editor.context_menu_visible())
13828        .await;
13829    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13830        editor
13831            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13832            .unwrap()
13833    });
13834    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
13835
13836    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13837        let task_completion_item = completion_item.clone();
13838        async move { Ok(task_completion_item) }
13839    })
13840    .next()
13841    .await
13842    .unwrap();
13843    apply_additional_edits.await.unwrap();
13844    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
13845}
13846
13847#[gpui::test]
13848async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
13849    init_test(cx, |_| {});
13850
13851    let mut cx = EditorLspTestContext::new_rust(
13852        lsp::ServerCapabilities {
13853            completion_provider: Some(lsp::CompletionOptions {
13854                trigger_characters: Some(vec![".".to_string()]),
13855                resolve_provider: Some(true),
13856                ..Default::default()
13857            }),
13858            ..Default::default()
13859        },
13860        cx,
13861    )
13862    .await;
13863
13864    cx.set_state("fn main() { let a = 2ˇ; }");
13865    cx.simulate_keystroke(".");
13866
13867    let item1 = lsp::CompletionItem {
13868        label: "method id()".to_string(),
13869        filter_text: Some("id".to_string()),
13870        detail: None,
13871        documentation: None,
13872        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13873            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13874            new_text: ".id".to_string(),
13875        })),
13876        ..lsp::CompletionItem::default()
13877    };
13878
13879    let item2 = lsp::CompletionItem {
13880        label: "other".to_string(),
13881        filter_text: Some("other".to_string()),
13882        detail: None,
13883        documentation: None,
13884        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13885            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13886            new_text: ".other".to_string(),
13887        })),
13888        ..lsp::CompletionItem::default()
13889    };
13890
13891    let item1 = item1.clone();
13892    cx.set_request_handler::<lsp::request::Completion, _, _>({
13893        let item1 = item1.clone();
13894        move |_, _, _| {
13895            let item1 = item1.clone();
13896            let item2 = item2.clone();
13897            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
13898        }
13899    })
13900    .next()
13901    .await;
13902
13903    cx.condition(|editor, _| editor.context_menu_visible())
13904        .await;
13905    cx.update_editor(|editor, _, _| {
13906        let context_menu = editor.context_menu.borrow_mut();
13907        let context_menu = context_menu
13908            .as_ref()
13909            .expect("Should have the context menu deployed");
13910        match context_menu {
13911            CodeContextMenu::Completions(completions_menu) => {
13912                let completions = completions_menu.completions.borrow_mut();
13913                assert_eq!(
13914                    completions
13915                        .iter()
13916                        .map(|completion| &completion.label.text)
13917                        .collect::<Vec<_>>(),
13918                    vec!["method id()", "other"]
13919                )
13920            }
13921            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13922        }
13923    });
13924
13925    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
13926        let item1 = item1.clone();
13927        move |_, item_to_resolve, _| {
13928            let item1 = item1.clone();
13929            async move {
13930                if item1 == item_to_resolve {
13931                    Ok(lsp::CompletionItem {
13932                        label: "method id()".to_string(),
13933                        filter_text: Some("id".to_string()),
13934                        detail: Some("Now resolved!".to_string()),
13935                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
13936                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13937                            range: lsp::Range::new(
13938                                lsp::Position::new(0, 22),
13939                                lsp::Position::new(0, 22),
13940                            ),
13941                            new_text: ".id".to_string(),
13942                        })),
13943                        ..lsp::CompletionItem::default()
13944                    })
13945                } else {
13946                    Ok(item_to_resolve)
13947                }
13948            }
13949        }
13950    })
13951    .next()
13952    .await
13953    .unwrap();
13954    cx.run_until_parked();
13955
13956    cx.update_editor(|editor, window, cx| {
13957        editor.context_menu_next(&Default::default(), window, cx);
13958    });
13959
13960    cx.update_editor(|editor, _, _| {
13961        let context_menu = editor.context_menu.borrow_mut();
13962        let context_menu = context_menu
13963            .as_ref()
13964            .expect("Should have the context menu deployed");
13965        match context_menu {
13966            CodeContextMenu::Completions(completions_menu) => {
13967                let completions = completions_menu.completions.borrow_mut();
13968                assert_eq!(
13969                    completions
13970                        .iter()
13971                        .map(|completion| &completion.label.text)
13972                        .collect::<Vec<_>>(),
13973                    vec!["method id() Now resolved!", "other"],
13974                    "Should update first completion label, but not second as the filter text did not match."
13975                );
13976            }
13977            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
13978        }
13979    });
13980}
13981
13982#[gpui::test]
13983async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
13984    init_test(cx, |_| {});
13985
13986    let mut cx = EditorLspTestContext::new_rust(
13987        lsp::ServerCapabilities {
13988            completion_provider: Some(lsp::CompletionOptions {
13989                trigger_characters: Some(vec![".".to_string()]),
13990                resolve_provider: Some(true),
13991                ..Default::default()
13992            }),
13993            ..Default::default()
13994        },
13995        cx,
13996    )
13997    .await;
13998
13999    cx.set_state("fn main() { let a = 2ˇ; }");
14000    cx.simulate_keystroke(".");
14001
14002    let unresolved_item_1 = lsp::CompletionItem {
14003        label: "id".to_string(),
14004        filter_text: Some("id".to_string()),
14005        detail: None,
14006        documentation: None,
14007        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14008            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14009            new_text: ".id".to_string(),
14010        })),
14011        ..lsp::CompletionItem::default()
14012    };
14013    let resolved_item_1 = lsp::CompletionItem {
14014        additional_text_edits: Some(vec![lsp::TextEdit {
14015            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14016            new_text: "!!".to_string(),
14017        }]),
14018        ..unresolved_item_1.clone()
14019    };
14020    let unresolved_item_2 = lsp::CompletionItem {
14021        label: "other".to_string(),
14022        filter_text: Some("other".to_string()),
14023        detail: None,
14024        documentation: None,
14025        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14026            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14027            new_text: ".other".to_string(),
14028        })),
14029        ..lsp::CompletionItem::default()
14030    };
14031    let resolved_item_2 = lsp::CompletionItem {
14032        additional_text_edits: Some(vec![lsp::TextEdit {
14033            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14034            new_text: "??".to_string(),
14035        }]),
14036        ..unresolved_item_2.clone()
14037    };
14038
14039    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14040    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14041    cx.lsp
14042        .server
14043        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14044            let unresolved_item_1 = unresolved_item_1.clone();
14045            let resolved_item_1 = resolved_item_1.clone();
14046            let unresolved_item_2 = unresolved_item_2.clone();
14047            let resolved_item_2 = resolved_item_2.clone();
14048            let resolve_requests_1 = resolve_requests_1.clone();
14049            let resolve_requests_2 = resolve_requests_2.clone();
14050            move |unresolved_request, _| {
14051                let unresolved_item_1 = unresolved_item_1.clone();
14052                let resolved_item_1 = resolved_item_1.clone();
14053                let unresolved_item_2 = unresolved_item_2.clone();
14054                let resolved_item_2 = resolved_item_2.clone();
14055                let resolve_requests_1 = resolve_requests_1.clone();
14056                let resolve_requests_2 = resolve_requests_2.clone();
14057                async move {
14058                    if unresolved_request == unresolved_item_1 {
14059                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14060                        Ok(resolved_item_1.clone())
14061                    } else if unresolved_request == unresolved_item_2 {
14062                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14063                        Ok(resolved_item_2.clone())
14064                    } else {
14065                        panic!("Unexpected completion item {unresolved_request:?}")
14066                    }
14067                }
14068            }
14069        })
14070        .detach();
14071
14072    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14073        let unresolved_item_1 = unresolved_item_1.clone();
14074        let unresolved_item_2 = unresolved_item_2.clone();
14075        async move {
14076            Ok(Some(lsp::CompletionResponse::Array(vec![
14077                unresolved_item_1,
14078                unresolved_item_2,
14079            ])))
14080        }
14081    })
14082    .next()
14083    .await;
14084
14085    cx.condition(|editor, _| editor.context_menu_visible())
14086        .await;
14087    cx.update_editor(|editor, _, _| {
14088        let context_menu = editor.context_menu.borrow_mut();
14089        let context_menu = context_menu
14090            .as_ref()
14091            .expect("Should have the context menu deployed");
14092        match context_menu {
14093            CodeContextMenu::Completions(completions_menu) => {
14094                let completions = completions_menu.completions.borrow_mut();
14095                assert_eq!(
14096                    completions
14097                        .iter()
14098                        .map(|completion| &completion.label.text)
14099                        .collect::<Vec<_>>(),
14100                    vec!["id", "other"]
14101                )
14102            }
14103            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14104        }
14105    });
14106    cx.run_until_parked();
14107
14108    cx.update_editor(|editor, window, cx| {
14109        editor.context_menu_next(&ContextMenuNext, window, cx);
14110    });
14111    cx.run_until_parked();
14112    cx.update_editor(|editor, window, cx| {
14113        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14114    });
14115    cx.run_until_parked();
14116    cx.update_editor(|editor, window, cx| {
14117        editor.context_menu_next(&ContextMenuNext, window, cx);
14118    });
14119    cx.run_until_parked();
14120    cx.update_editor(|editor, window, cx| {
14121        editor
14122            .compose_completion(&ComposeCompletion::default(), window, cx)
14123            .expect("No task returned")
14124    })
14125    .await
14126    .expect("Completion failed");
14127    cx.run_until_parked();
14128
14129    cx.update_editor(|editor, _, cx| {
14130        assert_eq!(
14131            resolve_requests_1.load(atomic::Ordering::Acquire),
14132            1,
14133            "Should always resolve once despite multiple selections"
14134        );
14135        assert_eq!(
14136            resolve_requests_2.load(atomic::Ordering::Acquire),
14137            1,
14138            "Should always resolve once after multiple selections and applying the completion"
14139        );
14140        assert_eq!(
14141            editor.text(cx),
14142            "fn main() { let a = ??.other; }",
14143            "Should use resolved data when applying the completion"
14144        );
14145    });
14146}
14147
14148#[gpui::test]
14149async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14150    init_test(cx, |_| {});
14151
14152    let item_0 = lsp::CompletionItem {
14153        label: "abs".into(),
14154        insert_text: Some("abs".into()),
14155        data: Some(json!({ "very": "special"})),
14156        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14157        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14158            lsp::InsertReplaceEdit {
14159                new_text: "abs".to_string(),
14160                insert: lsp::Range::default(),
14161                replace: lsp::Range::default(),
14162            },
14163        )),
14164        ..lsp::CompletionItem::default()
14165    };
14166    let items = iter::once(item_0.clone())
14167        .chain((11..51).map(|i| lsp::CompletionItem {
14168            label: format!("item_{}", i),
14169            insert_text: Some(format!("item_{}", i)),
14170            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14171            ..lsp::CompletionItem::default()
14172        }))
14173        .collect::<Vec<_>>();
14174
14175    let default_commit_characters = vec!["?".to_string()];
14176    let default_data = json!({ "default": "data"});
14177    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14178    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14179    let default_edit_range = lsp::Range {
14180        start: lsp::Position {
14181            line: 0,
14182            character: 5,
14183        },
14184        end: lsp::Position {
14185            line: 0,
14186            character: 5,
14187        },
14188    };
14189
14190    let mut cx = EditorLspTestContext::new_rust(
14191        lsp::ServerCapabilities {
14192            completion_provider: Some(lsp::CompletionOptions {
14193                trigger_characters: Some(vec![".".to_string()]),
14194                resolve_provider: Some(true),
14195                ..Default::default()
14196            }),
14197            ..Default::default()
14198        },
14199        cx,
14200    )
14201    .await;
14202
14203    cx.set_state("fn main() { let a = 2ˇ; }");
14204    cx.simulate_keystroke(".");
14205
14206    let completion_data = default_data.clone();
14207    let completion_characters = default_commit_characters.clone();
14208    let completion_items = items.clone();
14209    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14210        let default_data = completion_data.clone();
14211        let default_commit_characters = completion_characters.clone();
14212        let items = completion_items.clone();
14213        async move {
14214            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14215                items,
14216                item_defaults: Some(lsp::CompletionListItemDefaults {
14217                    data: Some(default_data.clone()),
14218                    commit_characters: Some(default_commit_characters.clone()),
14219                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14220                        default_edit_range,
14221                    )),
14222                    insert_text_format: Some(default_insert_text_format),
14223                    insert_text_mode: Some(default_insert_text_mode),
14224                }),
14225                ..lsp::CompletionList::default()
14226            })))
14227        }
14228    })
14229    .next()
14230    .await;
14231
14232    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14233    cx.lsp
14234        .server
14235        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14236            let closure_resolved_items = resolved_items.clone();
14237            move |item_to_resolve, _| {
14238                let closure_resolved_items = closure_resolved_items.clone();
14239                async move {
14240                    closure_resolved_items.lock().push(item_to_resolve.clone());
14241                    Ok(item_to_resolve)
14242                }
14243            }
14244        })
14245        .detach();
14246
14247    cx.condition(|editor, _| editor.context_menu_visible())
14248        .await;
14249    cx.run_until_parked();
14250    cx.update_editor(|editor, _, _| {
14251        let menu = editor.context_menu.borrow_mut();
14252        match menu.as_ref().expect("should have the completions menu") {
14253            CodeContextMenu::Completions(completions_menu) => {
14254                assert_eq!(
14255                    completions_menu
14256                        .entries
14257                        .borrow()
14258                        .iter()
14259                        .map(|mat| mat.string.clone())
14260                        .collect::<Vec<String>>(),
14261                    items
14262                        .iter()
14263                        .map(|completion| completion.label.clone())
14264                        .collect::<Vec<String>>()
14265                );
14266            }
14267            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14268        }
14269    });
14270    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14271    // with 4 from the end.
14272    assert_eq!(
14273        *resolved_items.lock(),
14274        [&items[0..16], &items[items.len() - 4..items.len()]]
14275            .concat()
14276            .iter()
14277            .cloned()
14278            .map(|mut item| {
14279                if item.data.is_none() {
14280                    item.data = Some(default_data.clone());
14281                }
14282                item
14283            })
14284            .collect::<Vec<lsp::CompletionItem>>(),
14285        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14286    );
14287    resolved_items.lock().clear();
14288
14289    cx.update_editor(|editor, window, cx| {
14290        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14291    });
14292    cx.run_until_parked();
14293    // Completions that have already been resolved are skipped.
14294    assert_eq!(
14295        *resolved_items.lock(),
14296        items[items.len() - 16..items.len() - 4]
14297            .iter()
14298            .cloned()
14299            .map(|mut item| {
14300                if item.data.is_none() {
14301                    item.data = Some(default_data.clone());
14302                }
14303                item
14304            })
14305            .collect::<Vec<lsp::CompletionItem>>()
14306    );
14307    resolved_items.lock().clear();
14308}
14309
14310#[gpui::test]
14311async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14312    init_test(cx, |_| {});
14313
14314    let mut cx = EditorLspTestContext::new(
14315        Language::new(
14316            LanguageConfig {
14317                matcher: LanguageMatcher {
14318                    path_suffixes: vec!["jsx".into()],
14319                    ..Default::default()
14320                },
14321                overrides: [(
14322                    "element".into(),
14323                    LanguageConfigOverride {
14324                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14325                        ..Default::default()
14326                    },
14327                )]
14328                .into_iter()
14329                .collect(),
14330                ..Default::default()
14331            },
14332            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14333        )
14334        .with_override_query("(jsx_self_closing_element) @element")
14335        .unwrap(),
14336        lsp::ServerCapabilities {
14337            completion_provider: Some(lsp::CompletionOptions {
14338                trigger_characters: Some(vec![":".to_string()]),
14339                ..Default::default()
14340            }),
14341            ..Default::default()
14342        },
14343        cx,
14344    )
14345    .await;
14346
14347    cx.lsp
14348        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14349            Ok(Some(lsp::CompletionResponse::Array(vec![
14350                lsp::CompletionItem {
14351                    label: "bg-blue".into(),
14352                    ..Default::default()
14353                },
14354                lsp::CompletionItem {
14355                    label: "bg-red".into(),
14356                    ..Default::default()
14357                },
14358                lsp::CompletionItem {
14359                    label: "bg-yellow".into(),
14360                    ..Default::default()
14361                },
14362            ])))
14363        });
14364
14365    cx.set_state(r#"<p class="bgˇ" />"#);
14366
14367    // Trigger completion when typing a dash, because the dash is an extra
14368    // word character in the 'element' scope, which contains the cursor.
14369    cx.simulate_keystroke("-");
14370    cx.executor().run_until_parked();
14371    cx.update_editor(|editor, _, _| {
14372        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14373        {
14374            assert_eq!(
14375                completion_menu_entries(&menu),
14376                &["bg-red", "bg-blue", "bg-yellow"]
14377            );
14378        } else {
14379            panic!("expected completion menu to be open");
14380        }
14381    });
14382
14383    cx.simulate_keystroke("l");
14384    cx.executor().run_until_parked();
14385    cx.update_editor(|editor, _, _| {
14386        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14387        {
14388            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14389        } else {
14390            panic!("expected completion menu to be open");
14391        }
14392    });
14393
14394    // When filtering completions, consider the character after the '-' to
14395    // be the start of a subword.
14396    cx.set_state(r#"<p class="yelˇ" />"#);
14397    cx.simulate_keystroke("l");
14398    cx.executor().run_until_parked();
14399    cx.update_editor(|editor, _, _| {
14400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14401        {
14402            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14403        } else {
14404            panic!("expected completion menu to be open");
14405        }
14406    });
14407}
14408
14409fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14410    let entries = menu.entries.borrow();
14411    entries.iter().map(|mat| mat.string.clone()).collect()
14412}
14413
14414#[gpui::test]
14415async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14416    init_test(cx, |settings| {
14417        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14418            FormatterList(vec![Formatter::Prettier].into()),
14419        ))
14420    });
14421
14422    let fs = FakeFs::new(cx.executor());
14423    fs.insert_file(path!("/file.ts"), Default::default()).await;
14424
14425    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14426    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14427
14428    language_registry.add(Arc::new(Language::new(
14429        LanguageConfig {
14430            name: "TypeScript".into(),
14431            matcher: LanguageMatcher {
14432                path_suffixes: vec!["ts".to_string()],
14433                ..Default::default()
14434            },
14435            ..Default::default()
14436        },
14437        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14438    )));
14439    update_test_language_settings(cx, |settings| {
14440        settings.defaults.prettier = Some(PrettierSettings {
14441            allowed: true,
14442            ..PrettierSettings::default()
14443        });
14444    });
14445
14446    let test_plugin = "test_plugin";
14447    let _ = language_registry.register_fake_lsp(
14448        "TypeScript",
14449        FakeLspAdapter {
14450            prettier_plugins: vec![test_plugin],
14451            ..Default::default()
14452        },
14453    );
14454
14455    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14456    let buffer = project
14457        .update(cx, |project, cx| {
14458            project.open_local_buffer(path!("/file.ts"), cx)
14459        })
14460        .await
14461        .unwrap();
14462
14463    let buffer_text = "one\ntwo\nthree\n";
14464    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14465    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14466    editor.update_in(cx, |editor, window, cx| {
14467        editor.set_text(buffer_text, window, cx)
14468    });
14469
14470    editor
14471        .update_in(cx, |editor, window, cx| {
14472            editor.perform_format(
14473                project.clone(),
14474                FormatTrigger::Manual,
14475                FormatTarget::Buffers,
14476                window,
14477                cx,
14478            )
14479        })
14480        .unwrap()
14481        .await;
14482    assert_eq!(
14483        editor.update(cx, |editor, cx| editor.text(cx)),
14484        buffer_text.to_string() + prettier_format_suffix,
14485        "Test prettier formatting was not applied to the original buffer text",
14486    );
14487
14488    update_test_language_settings(cx, |settings| {
14489        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14490    });
14491    let format = editor.update_in(cx, |editor, window, cx| {
14492        editor.perform_format(
14493            project.clone(),
14494            FormatTrigger::Manual,
14495            FormatTarget::Buffers,
14496            window,
14497            cx,
14498        )
14499    });
14500    format.await.unwrap();
14501    assert_eq!(
14502        editor.update(cx, |editor, cx| editor.text(cx)),
14503        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14504        "Autoformatting (via test prettier) was not applied to the original buffer text",
14505    );
14506}
14507
14508#[gpui::test]
14509async fn test_addition_reverts(cx: &mut TestAppContext) {
14510    init_test(cx, |_| {});
14511    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14512    let base_text = indoc! {r#"
14513        struct Row;
14514        struct Row1;
14515        struct Row2;
14516
14517        struct Row4;
14518        struct Row5;
14519        struct Row6;
14520
14521        struct Row8;
14522        struct Row9;
14523        struct Row10;"#};
14524
14525    // When addition hunks are not adjacent to carets, no hunk revert is performed
14526    assert_hunk_revert(
14527        indoc! {r#"struct Row;
14528                   struct Row1;
14529                   struct Row1.1;
14530                   struct Row1.2;
14531                   struct Row2;ˇ
14532
14533                   struct Row4;
14534                   struct Row5;
14535                   struct Row6;
14536
14537                   struct Row8;
14538                   ˇstruct Row9;
14539                   struct Row9.1;
14540                   struct Row9.2;
14541                   struct Row9.3;
14542                   struct Row10;"#},
14543        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14544        indoc! {r#"struct Row;
14545                   struct Row1;
14546                   struct Row1.1;
14547                   struct Row1.2;
14548                   struct Row2;ˇ
14549
14550                   struct Row4;
14551                   struct Row5;
14552                   struct Row6;
14553
14554                   struct Row8;
14555                   ˇstruct Row9;
14556                   struct Row9.1;
14557                   struct Row9.2;
14558                   struct Row9.3;
14559                   struct Row10;"#},
14560        base_text,
14561        &mut cx,
14562    );
14563    // Same for selections
14564    assert_hunk_revert(
14565        indoc! {r#"struct Row;
14566                   struct Row1;
14567                   struct Row2;
14568                   struct Row2.1;
14569                   struct Row2.2;
14570                   «ˇ
14571                   struct Row4;
14572                   struct» Row5;
14573                   «struct Row6;
14574                   ˇ»
14575                   struct Row9.1;
14576                   struct Row9.2;
14577                   struct Row9.3;
14578                   struct Row8;
14579                   struct Row9;
14580                   struct Row10;"#},
14581        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14582        indoc! {r#"struct Row;
14583                   struct Row1;
14584                   struct Row2;
14585                   struct Row2.1;
14586                   struct Row2.2;
14587                   «ˇ
14588                   struct Row4;
14589                   struct» Row5;
14590                   «struct Row6;
14591                   ˇ»
14592                   struct Row9.1;
14593                   struct Row9.2;
14594                   struct Row9.3;
14595                   struct Row8;
14596                   struct Row9;
14597                   struct Row10;"#},
14598        base_text,
14599        &mut cx,
14600    );
14601
14602    // When carets and selections intersect the addition hunks, those are reverted.
14603    // Adjacent carets got merged.
14604    assert_hunk_revert(
14605        indoc! {r#"struct Row;
14606                   ˇ// something on the top
14607                   struct Row1;
14608                   struct Row2;
14609                   struct Roˇw3.1;
14610                   struct Row2.2;
14611                   struct Row2.3;ˇ
14612
14613                   struct Row4;
14614                   struct ˇRow5.1;
14615                   struct Row5.2;
14616                   struct «Rowˇ»5.3;
14617                   struct Row5;
14618                   struct Row6;
14619                   ˇ
14620                   struct Row9.1;
14621                   struct «Rowˇ»9.2;
14622                   struct «ˇRow»9.3;
14623                   struct Row8;
14624                   struct Row9;
14625                   «ˇ// something on bottom»
14626                   struct Row10;"#},
14627        vec![
14628            DiffHunkStatusKind::Added,
14629            DiffHunkStatusKind::Added,
14630            DiffHunkStatusKind::Added,
14631            DiffHunkStatusKind::Added,
14632            DiffHunkStatusKind::Added,
14633        ],
14634        indoc! {r#"struct Row;
14635                   ˇstruct Row1;
14636                   struct Row2;
14637                   ˇ
14638                   struct Row4;
14639                   ˇstruct Row5;
14640                   struct Row6;
14641                   ˇ
14642                   ˇstruct Row8;
14643                   struct Row9;
14644                   ˇstruct Row10;"#},
14645        base_text,
14646        &mut cx,
14647    );
14648}
14649
14650#[gpui::test]
14651async fn test_modification_reverts(cx: &mut TestAppContext) {
14652    init_test(cx, |_| {});
14653    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14654    let base_text = indoc! {r#"
14655        struct Row;
14656        struct Row1;
14657        struct Row2;
14658
14659        struct Row4;
14660        struct Row5;
14661        struct Row6;
14662
14663        struct Row8;
14664        struct Row9;
14665        struct Row10;"#};
14666
14667    // Modification hunks behave the same as the addition ones.
14668    assert_hunk_revert(
14669        indoc! {r#"struct Row;
14670                   struct Row1;
14671                   struct Row33;
14672                   ˇ
14673                   struct Row4;
14674                   struct Row5;
14675                   struct Row6;
14676                   ˇ
14677                   struct Row99;
14678                   struct Row9;
14679                   struct Row10;"#},
14680        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14681        indoc! {r#"struct Row;
14682                   struct Row1;
14683                   struct Row33;
14684                   ˇ
14685                   struct Row4;
14686                   struct Row5;
14687                   struct Row6;
14688                   ˇ
14689                   struct Row99;
14690                   struct Row9;
14691                   struct Row10;"#},
14692        base_text,
14693        &mut cx,
14694    );
14695    assert_hunk_revert(
14696        indoc! {r#"struct Row;
14697                   struct Row1;
14698                   struct Row33;
14699                   «ˇ
14700                   struct Row4;
14701                   struct» Row5;
14702                   «struct Row6;
14703                   ˇ»
14704                   struct Row99;
14705                   struct Row9;
14706                   struct Row10;"#},
14707        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
14708        indoc! {r#"struct Row;
14709                   struct Row1;
14710                   struct Row33;
14711                   «ˇ
14712                   struct Row4;
14713                   struct» Row5;
14714                   «struct Row6;
14715                   ˇ»
14716                   struct Row99;
14717                   struct Row9;
14718                   struct Row10;"#},
14719        base_text,
14720        &mut cx,
14721    );
14722
14723    assert_hunk_revert(
14724        indoc! {r#"ˇstruct Row1.1;
14725                   struct Row1;
14726                   «ˇstr»uct Row22;
14727
14728                   struct ˇRow44;
14729                   struct Row5;
14730                   struct «Rˇ»ow66;ˇ
14731
14732                   «struˇ»ct Row88;
14733                   struct Row9;
14734                   struct Row1011;ˇ"#},
14735        vec![
14736            DiffHunkStatusKind::Modified,
14737            DiffHunkStatusKind::Modified,
14738            DiffHunkStatusKind::Modified,
14739            DiffHunkStatusKind::Modified,
14740            DiffHunkStatusKind::Modified,
14741            DiffHunkStatusKind::Modified,
14742        ],
14743        indoc! {r#"struct Row;
14744                   ˇstruct Row1;
14745                   struct Row2;
14746                   ˇ
14747                   struct Row4;
14748                   ˇstruct Row5;
14749                   struct Row6;
14750                   ˇ
14751                   struct Row8;
14752                   ˇstruct Row9;
14753                   struct Row10;ˇ"#},
14754        base_text,
14755        &mut cx,
14756    );
14757}
14758
14759#[gpui::test]
14760async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
14761    init_test(cx, |_| {});
14762    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14763    let base_text = indoc! {r#"
14764        one
14765
14766        two
14767        three
14768        "#};
14769
14770    cx.set_head_text(base_text);
14771    cx.set_state("\nˇ\n");
14772    cx.executor().run_until_parked();
14773    cx.update_editor(|editor, _window, cx| {
14774        editor.expand_selected_diff_hunks(cx);
14775    });
14776    cx.executor().run_until_parked();
14777    cx.update_editor(|editor, window, cx| {
14778        editor.backspace(&Default::default(), window, cx);
14779    });
14780    cx.run_until_parked();
14781    cx.assert_state_with_diff(
14782        indoc! {r#"
14783
14784        - two
14785        - threeˇ
14786        +
14787        "#}
14788        .to_string(),
14789    );
14790}
14791
14792#[gpui::test]
14793async fn test_deletion_reverts(cx: &mut TestAppContext) {
14794    init_test(cx, |_| {});
14795    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14796    let base_text = indoc! {r#"struct Row;
14797struct Row1;
14798struct Row2;
14799
14800struct Row4;
14801struct Row5;
14802struct Row6;
14803
14804struct Row8;
14805struct Row9;
14806struct Row10;"#};
14807
14808    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
14809    assert_hunk_revert(
14810        indoc! {r#"struct Row;
14811                   struct Row2;
14812
14813                   ˇstruct Row4;
14814                   struct Row5;
14815                   struct Row6;
14816                   ˇ
14817                   struct Row8;
14818                   struct Row10;"#},
14819        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14820        indoc! {r#"struct Row;
14821                   struct Row2;
14822
14823                   ˇstruct Row4;
14824                   struct Row5;
14825                   struct Row6;
14826                   ˇ
14827                   struct Row8;
14828                   struct Row10;"#},
14829        base_text,
14830        &mut cx,
14831    );
14832    assert_hunk_revert(
14833        indoc! {r#"struct Row;
14834                   struct Row2;
14835
14836                   «ˇstruct Row4;
14837                   struct» Row5;
14838                   «struct Row6;
14839                   ˇ»
14840                   struct Row8;
14841                   struct Row10;"#},
14842        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14843        indoc! {r#"struct Row;
14844                   struct Row2;
14845
14846                   «ˇstruct Row4;
14847                   struct» Row5;
14848                   «struct Row6;
14849                   ˇ»
14850                   struct Row8;
14851                   struct Row10;"#},
14852        base_text,
14853        &mut cx,
14854    );
14855
14856    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
14857    assert_hunk_revert(
14858        indoc! {r#"struct Row;
14859                   ˇstruct Row2;
14860
14861                   struct Row4;
14862                   struct Row5;
14863                   struct Row6;
14864
14865                   struct Row8;ˇ
14866                   struct Row10;"#},
14867        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
14868        indoc! {r#"struct Row;
14869                   struct Row1;
14870                   ˇstruct Row2;
14871
14872                   struct Row4;
14873                   struct Row5;
14874                   struct Row6;
14875
14876                   struct Row8;ˇ
14877                   struct Row9;
14878                   struct Row10;"#},
14879        base_text,
14880        &mut cx,
14881    );
14882    assert_hunk_revert(
14883        indoc! {r#"struct Row;
14884                   struct Row2«ˇ;
14885                   struct Row4;
14886                   struct» Row5;
14887                   «struct Row6;
14888
14889                   struct Row8;ˇ»
14890                   struct Row10;"#},
14891        vec![
14892            DiffHunkStatusKind::Deleted,
14893            DiffHunkStatusKind::Deleted,
14894            DiffHunkStatusKind::Deleted,
14895        ],
14896        indoc! {r#"struct Row;
14897                   struct Row1;
14898                   struct Row2«ˇ;
14899
14900                   struct Row4;
14901                   struct» Row5;
14902                   «struct Row6;
14903
14904                   struct Row8;ˇ»
14905                   struct Row9;
14906                   struct Row10;"#},
14907        base_text,
14908        &mut cx,
14909    );
14910}
14911
14912#[gpui::test]
14913async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
14914    init_test(cx, |_| {});
14915
14916    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
14917    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
14918    let base_text_3 =
14919        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
14920
14921    let text_1 = edit_first_char_of_every_line(base_text_1);
14922    let text_2 = edit_first_char_of_every_line(base_text_2);
14923    let text_3 = edit_first_char_of_every_line(base_text_3);
14924
14925    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
14926    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
14927    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
14928
14929    let multibuffer = cx.new(|cx| {
14930        let mut multibuffer = MultiBuffer::new(ReadWrite);
14931        multibuffer.push_excerpts(
14932            buffer_1.clone(),
14933            [
14934                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14935                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14936                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14937            ],
14938            cx,
14939        );
14940        multibuffer.push_excerpts(
14941            buffer_2.clone(),
14942            [
14943                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14944                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14945                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14946            ],
14947            cx,
14948        );
14949        multibuffer.push_excerpts(
14950            buffer_3.clone(),
14951            [
14952                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
14953                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
14954                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
14955            ],
14956            cx,
14957        );
14958        multibuffer
14959    });
14960
14961    let fs = FakeFs::new(cx.executor());
14962    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14963    let (editor, cx) = cx
14964        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
14965    editor.update_in(cx, |editor, _window, cx| {
14966        for (buffer, diff_base) in [
14967            (buffer_1.clone(), base_text_1),
14968            (buffer_2.clone(), base_text_2),
14969            (buffer_3.clone(), base_text_3),
14970        ] {
14971            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14972            editor
14973                .buffer
14974                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14975        }
14976    });
14977    cx.executor().run_until_parked();
14978
14979    editor.update_in(cx, |editor, window, cx| {
14980        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}");
14981        editor.select_all(&SelectAll, window, cx);
14982        editor.git_restore(&Default::default(), window, cx);
14983    });
14984    cx.executor().run_until_parked();
14985
14986    // When all ranges are selected, all buffer hunks are reverted.
14987    editor.update(cx, |editor, cx| {
14988        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");
14989    });
14990    buffer_1.update(cx, |buffer, _| {
14991        assert_eq!(buffer.text(), base_text_1);
14992    });
14993    buffer_2.update(cx, |buffer, _| {
14994        assert_eq!(buffer.text(), base_text_2);
14995    });
14996    buffer_3.update(cx, |buffer, _| {
14997        assert_eq!(buffer.text(), base_text_3);
14998    });
14999
15000    editor.update_in(cx, |editor, window, cx| {
15001        editor.undo(&Default::default(), window, cx);
15002    });
15003
15004    editor.update_in(cx, |editor, window, cx| {
15005        editor.change_selections(None, window, cx, |s| {
15006            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15007        });
15008        editor.git_restore(&Default::default(), window, cx);
15009    });
15010
15011    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15012    // but not affect buffer_2 and its related excerpts.
15013    editor.update(cx, |editor, cx| {
15014        assert_eq!(
15015            editor.text(cx),
15016            "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}"
15017        );
15018    });
15019    buffer_1.update(cx, |buffer, _| {
15020        assert_eq!(buffer.text(), base_text_1);
15021    });
15022    buffer_2.update(cx, |buffer, _| {
15023        assert_eq!(
15024            buffer.text(),
15025            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15026        );
15027    });
15028    buffer_3.update(cx, |buffer, _| {
15029        assert_eq!(
15030            buffer.text(),
15031            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15032        );
15033    });
15034
15035    fn edit_first_char_of_every_line(text: &str) -> String {
15036        text.split('\n')
15037            .map(|line| format!("X{}", &line[1..]))
15038            .collect::<Vec<_>>()
15039            .join("\n")
15040    }
15041}
15042
15043#[gpui::test]
15044async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15045    init_test(cx, |_| {});
15046
15047    let cols = 4;
15048    let rows = 10;
15049    let sample_text_1 = sample_text(rows, cols, 'a');
15050    assert_eq!(
15051        sample_text_1,
15052        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15053    );
15054    let sample_text_2 = sample_text(rows, cols, 'l');
15055    assert_eq!(
15056        sample_text_2,
15057        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15058    );
15059    let sample_text_3 = sample_text(rows, cols, 'v');
15060    assert_eq!(
15061        sample_text_3,
15062        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15063    );
15064
15065    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15066    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15067    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15068
15069    let multi_buffer = cx.new(|cx| {
15070        let mut multibuffer = MultiBuffer::new(ReadWrite);
15071        multibuffer.push_excerpts(
15072            buffer_1.clone(),
15073            [
15074                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15075                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15076                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15077            ],
15078            cx,
15079        );
15080        multibuffer.push_excerpts(
15081            buffer_2.clone(),
15082            [
15083                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15084                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15085                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15086            ],
15087            cx,
15088        );
15089        multibuffer.push_excerpts(
15090            buffer_3.clone(),
15091            [
15092                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15093                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15094                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15095            ],
15096            cx,
15097        );
15098        multibuffer
15099    });
15100
15101    let fs = FakeFs::new(cx.executor());
15102    fs.insert_tree(
15103        "/a",
15104        json!({
15105            "main.rs": sample_text_1,
15106            "other.rs": sample_text_2,
15107            "lib.rs": sample_text_3,
15108        }),
15109    )
15110    .await;
15111    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15112    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15113    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15114    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15115        Editor::new(
15116            EditorMode::full(),
15117            multi_buffer,
15118            Some(project.clone()),
15119            window,
15120            cx,
15121        )
15122    });
15123    let multibuffer_item_id = workspace
15124        .update(cx, |workspace, window, cx| {
15125            assert!(
15126                workspace.active_item(cx).is_none(),
15127                "active item should be None before the first item is added"
15128            );
15129            workspace.add_item_to_active_pane(
15130                Box::new(multi_buffer_editor.clone()),
15131                None,
15132                true,
15133                window,
15134                cx,
15135            );
15136            let active_item = workspace
15137                .active_item(cx)
15138                .expect("should have an active item after adding the multi buffer");
15139            assert!(
15140                !active_item.is_singleton(cx),
15141                "A multi buffer was expected to active after adding"
15142            );
15143            active_item.item_id()
15144        })
15145        .unwrap();
15146    cx.executor().run_until_parked();
15147
15148    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15149        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15150            s.select_ranges(Some(1..2))
15151        });
15152        editor.open_excerpts(&OpenExcerpts, window, cx);
15153    });
15154    cx.executor().run_until_parked();
15155    let first_item_id = workspace
15156        .update(cx, |workspace, window, cx| {
15157            let active_item = workspace
15158                .active_item(cx)
15159                .expect("should have an active item after navigating into the 1st buffer");
15160            let first_item_id = active_item.item_id();
15161            assert_ne!(
15162                first_item_id, multibuffer_item_id,
15163                "Should navigate into the 1st buffer and activate it"
15164            );
15165            assert!(
15166                active_item.is_singleton(cx),
15167                "New active item should be a singleton buffer"
15168            );
15169            assert_eq!(
15170                active_item
15171                    .act_as::<Editor>(cx)
15172                    .expect("should have navigated into an editor for the 1st buffer")
15173                    .read(cx)
15174                    .text(cx),
15175                sample_text_1
15176            );
15177
15178            workspace
15179                .go_back(workspace.active_pane().downgrade(), window, cx)
15180                .detach_and_log_err(cx);
15181
15182            first_item_id
15183        })
15184        .unwrap();
15185    cx.executor().run_until_parked();
15186    workspace
15187        .update(cx, |workspace, _, cx| {
15188            let active_item = workspace
15189                .active_item(cx)
15190                .expect("should have an active item after navigating back");
15191            assert_eq!(
15192                active_item.item_id(),
15193                multibuffer_item_id,
15194                "Should navigate back to the multi buffer"
15195            );
15196            assert!(!active_item.is_singleton(cx));
15197        })
15198        .unwrap();
15199
15200    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15201        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15202            s.select_ranges(Some(39..40))
15203        });
15204        editor.open_excerpts(&OpenExcerpts, window, cx);
15205    });
15206    cx.executor().run_until_parked();
15207    let second_item_id = workspace
15208        .update(cx, |workspace, window, cx| {
15209            let active_item = workspace
15210                .active_item(cx)
15211                .expect("should have an active item after navigating into the 2nd buffer");
15212            let second_item_id = active_item.item_id();
15213            assert_ne!(
15214                second_item_id, multibuffer_item_id,
15215                "Should navigate away from the multibuffer"
15216            );
15217            assert_ne!(
15218                second_item_id, first_item_id,
15219                "Should navigate into the 2nd buffer and activate it"
15220            );
15221            assert!(
15222                active_item.is_singleton(cx),
15223                "New active item should be a singleton buffer"
15224            );
15225            assert_eq!(
15226                active_item
15227                    .act_as::<Editor>(cx)
15228                    .expect("should have navigated into an editor")
15229                    .read(cx)
15230                    .text(cx),
15231                sample_text_2
15232            );
15233
15234            workspace
15235                .go_back(workspace.active_pane().downgrade(), window, cx)
15236                .detach_and_log_err(cx);
15237
15238            second_item_id
15239        })
15240        .unwrap();
15241    cx.executor().run_until_parked();
15242    workspace
15243        .update(cx, |workspace, _, cx| {
15244            let active_item = workspace
15245                .active_item(cx)
15246                .expect("should have an active item after navigating back from the 2nd buffer");
15247            assert_eq!(
15248                active_item.item_id(),
15249                multibuffer_item_id,
15250                "Should navigate back from the 2nd buffer to the multi buffer"
15251            );
15252            assert!(!active_item.is_singleton(cx));
15253        })
15254        .unwrap();
15255
15256    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15257        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15258            s.select_ranges(Some(70..70))
15259        });
15260        editor.open_excerpts(&OpenExcerpts, window, cx);
15261    });
15262    cx.executor().run_until_parked();
15263    workspace
15264        .update(cx, |workspace, window, cx| {
15265            let active_item = workspace
15266                .active_item(cx)
15267                .expect("should have an active item after navigating into the 3rd buffer");
15268            let third_item_id = active_item.item_id();
15269            assert_ne!(
15270                third_item_id, multibuffer_item_id,
15271                "Should navigate into the 3rd buffer and activate it"
15272            );
15273            assert_ne!(third_item_id, first_item_id);
15274            assert_ne!(third_item_id, second_item_id);
15275            assert!(
15276                active_item.is_singleton(cx),
15277                "New active item should be a singleton buffer"
15278            );
15279            assert_eq!(
15280                active_item
15281                    .act_as::<Editor>(cx)
15282                    .expect("should have navigated into an editor")
15283                    .read(cx)
15284                    .text(cx),
15285                sample_text_3
15286            );
15287
15288            workspace
15289                .go_back(workspace.active_pane().downgrade(), window, cx)
15290                .detach_and_log_err(cx);
15291        })
15292        .unwrap();
15293    cx.executor().run_until_parked();
15294    workspace
15295        .update(cx, |workspace, _, cx| {
15296            let active_item = workspace
15297                .active_item(cx)
15298                .expect("should have an active item after navigating back from the 3rd buffer");
15299            assert_eq!(
15300                active_item.item_id(),
15301                multibuffer_item_id,
15302                "Should navigate back from the 3rd buffer to the multi buffer"
15303            );
15304            assert!(!active_item.is_singleton(cx));
15305        })
15306        .unwrap();
15307}
15308
15309#[gpui::test]
15310async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15311    init_test(cx, |_| {});
15312
15313    let mut cx = EditorTestContext::new(cx).await;
15314
15315    let diff_base = r#"
15316        use some::mod;
15317
15318        const A: u32 = 42;
15319
15320        fn main() {
15321            println!("hello");
15322
15323            println!("world");
15324        }
15325        "#
15326    .unindent();
15327
15328    cx.set_state(
15329        &r#"
15330        use some::modified;
15331
15332        ˇ
15333        fn main() {
15334            println!("hello there");
15335
15336            println!("around the");
15337            println!("world");
15338        }
15339        "#
15340        .unindent(),
15341    );
15342
15343    cx.set_head_text(&diff_base);
15344    executor.run_until_parked();
15345
15346    cx.update_editor(|editor, window, cx| {
15347        editor.go_to_next_hunk(&GoToHunk, window, cx);
15348        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15349    });
15350    executor.run_until_parked();
15351    cx.assert_state_with_diff(
15352        r#"
15353          use some::modified;
15354
15355
15356          fn main() {
15357        -     println!("hello");
15358        + ˇ    println!("hello there");
15359
15360              println!("around the");
15361              println!("world");
15362          }
15363        "#
15364        .unindent(),
15365    );
15366
15367    cx.update_editor(|editor, window, cx| {
15368        for _ in 0..2 {
15369            editor.go_to_next_hunk(&GoToHunk, window, cx);
15370            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15371        }
15372    });
15373    executor.run_until_parked();
15374    cx.assert_state_with_diff(
15375        r#"
15376        - use some::mod;
15377        + ˇuse some::modified;
15378
15379
15380          fn main() {
15381        -     println!("hello");
15382        +     println!("hello there");
15383
15384        +     println!("around the");
15385              println!("world");
15386          }
15387        "#
15388        .unindent(),
15389    );
15390
15391    cx.update_editor(|editor, window, cx| {
15392        editor.go_to_next_hunk(&GoToHunk, window, cx);
15393        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15394    });
15395    executor.run_until_parked();
15396    cx.assert_state_with_diff(
15397        r#"
15398        - use some::mod;
15399        + use some::modified;
15400
15401        - const A: u32 = 42;
15402          ˇ
15403          fn main() {
15404        -     println!("hello");
15405        +     println!("hello there");
15406
15407        +     println!("around the");
15408              println!("world");
15409          }
15410        "#
15411        .unindent(),
15412    );
15413
15414    cx.update_editor(|editor, window, cx| {
15415        editor.cancel(&Cancel, window, cx);
15416    });
15417
15418    cx.assert_state_with_diff(
15419        r#"
15420          use some::modified;
15421
15422          ˇ
15423          fn main() {
15424              println!("hello there");
15425
15426              println!("around the");
15427              println!("world");
15428          }
15429        "#
15430        .unindent(),
15431    );
15432}
15433
15434#[gpui::test]
15435async fn test_diff_base_change_with_expanded_diff_hunks(
15436    executor: BackgroundExecutor,
15437    cx: &mut TestAppContext,
15438) {
15439    init_test(cx, |_| {});
15440
15441    let mut cx = EditorTestContext::new(cx).await;
15442
15443    let diff_base = r#"
15444        use some::mod1;
15445        use some::mod2;
15446
15447        const A: u32 = 42;
15448        const B: u32 = 42;
15449        const C: u32 = 42;
15450
15451        fn main() {
15452            println!("hello");
15453
15454            println!("world");
15455        }
15456        "#
15457    .unindent();
15458
15459    cx.set_state(
15460        &r#"
15461        use some::mod2;
15462
15463        const A: u32 = 42;
15464        const C: u32 = 42;
15465
15466        fn main(ˇ) {
15467            //println!("hello");
15468
15469            println!("world");
15470            //
15471            //
15472        }
15473        "#
15474        .unindent(),
15475    );
15476
15477    cx.set_head_text(&diff_base);
15478    executor.run_until_parked();
15479
15480    cx.update_editor(|editor, window, cx| {
15481        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15482    });
15483    executor.run_until_parked();
15484    cx.assert_state_with_diff(
15485        r#"
15486        - use some::mod1;
15487          use some::mod2;
15488
15489          const A: u32 = 42;
15490        - const B: u32 = 42;
15491          const C: u32 = 42;
15492
15493          fn main(ˇ) {
15494        -     println!("hello");
15495        +     //println!("hello");
15496
15497              println!("world");
15498        +     //
15499        +     //
15500          }
15501        "#
15502        .unindent(),
15503    );
15504
15505    cx.set_head_text("new diff base!");
15506    executor.run_until_parked();
15507    cx.assert_state_with_diff(
15508        r#"
15509        - new diff base!
15510        + use some::mod2;
15511        +
15512        + const A: u32 = 42;
15513        + const C: u32 = 42;
15514        +
15515        + fn main(ˇ) {
15516        +     //println!("hello");
15517        +
15518        +     println!("world");
15519        +     //
15520        +     //
15521        + }
15522        "#
15523        .unindent(),
15524    );
15525}
15526
15527#[gpui::test]
15528async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15529    init_test(cx, |_| {});
15530
15531    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15532    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15533    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15534    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15535    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15536    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15537
15538    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15539    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15540    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15541
15542    let multi_buffer = cx.new(|cx| {
15543        let mut multibuffer = MultiBuffer::new(ReadWrite);
15544        multibuffer.push_excerpts(
15545            buffer_1.clone(),
15546            [
15547                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15548                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15549                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15550            ],
15551            cx,
15552        );
15553        multibuffer.push_excerpts(
15554            buffer_2.clone(),
15555            [
15556                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15557                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15558                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15559            ],
15560            cx,
15561        );
15562        multibuffer.push_excerpts(
15563            buffer_3.clone(),
15564            [
15565                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15566                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15567                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15568            ],
15569            cx,
15570        );
15571        multibuffer
15572    });
15573
15574    let editor =
15575        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15576    editor
15577        .update(cx, |editor, _window, cx| {
15578            for (buffer, diff_base) in [
15579                (buffer_1.clone(), file_1_old),
15580                (buffer_2.clone(), file_2_old),
15581                (buffer_3.clone(), file_3_old),
15582            ] {
15583                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15584                editor
15585                    .buffer
15586                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15587            }
15588        })
15589        .unwrap();
15590
15591    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15592    cx.run_until_parked();
15593
15594    cx.assert_editor_state(
15595        &"
15596            ˇaaa
15597            ccc
15598            ddd
15599
15600            ggg
15601            hhh
15602
15603
15604            lll
15605            mmm
15606            NNN
15607
15608            qqq
15609            rrr
15610
15611            uuu
15612            111
15613            222
15614            333
15615
15616            666
15617            777
15618
15619            000
15620            !!!"
15621        .unindent(),
15622    );
15623
15624    cx.update_editor(|editor, window, cx| {
15625        editor.select_all(&SelectAll, window, cx);
15626        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15627    });
15628    cx.executor().run_until_parked();
15629
15630    cx.assert_state_with_diff(
15631        "
15632            «aaa
15633          - bbb
15634            ccc
15635            ddd
15636
15637            ggg
15638            hhh
15639
15640
15641            lll
15642            mmm
15643          - nnn
15644          + NNN
15645
15646            qqq
15647            rrr
15648
15649            uuu
15650            111
15651            222
15652            333
15653
15654          + 666
15655            777
15656
15657            000
15658            !!!ˇ»"
15659            .unindent(),
15660    );
15661}
15662
15663#[gpui::test]
15664async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
15665    init_test(cx, |_| {});
15666
15667    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
15668    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
15669
15670    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
15671    let multi_buffer = cx.new(|cx| {
15672        let mut multibuffer = MultiBuffer::new(ReadWrite);
15673        multibuffer.push_excerpts(
15674            buffer.clone(),
15675            [
15676                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
15677                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
15678                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
15679            ],
15680            cx,
15681        );
15682        multibuffer
15683    });
15684
15685    let editor =
15686        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15687    editor
15688        .update(cx, |editor, _window, cx| {
15689            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
15690            editor
15691                .buffer
15692                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
15693        })
15694        .unwrap();
15695
15696    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15697    cx.run_until_parked();
15698
15699    cx.update_editor(|editor, window, cx| {
15700        editor.expand_all_diff_hunks(&Default::default(), window, cx)
15701    });
15702    cx.executor().run_until_parked();
15703
15704    // When the start of a hunk coincides with the start of its excerpt,
15705    // the hunk is expanded. When the start of a a hunk is earlier than
15706    // the start of its excerpt, the hunk is not expanded.
15707    cx.assert_state_with_diff(
15708        "
15709            ˇaaa
15710          - bbb
15711          + BBB
15712
15713          - ddd
15714          - eee
15715          + DDD
15716          + EEE
15717            fff
15718
15719            iii
15720        "
15721        .unindent(),
15722    );
15723}
15724
15725#[gpui::test]
15726async fn test_edits_around_expanded_insertion_hunks(
15727    executor: BackgroundExecutor,
15728    cx: &mut TestAppContext,
15729) {
15730    init_test(cx, |_| {});
15731
15732    let mut cx = EditorTestContext::new(cx).await;
15733
15734    let diff_base = r#"
15735        use some::mod1;
15736        use some::mod2;
15737
15738        const A: u32 = 42;
15739
15740        fn main() {
15741            println!("hello");
15742
15743            println!("world");
15744        }
15745        "#
15746    .unindent();
15747    executor.run_until_parked();
15748    cx.set_state(
15749        &r#"
15750        use some::mod1;
15751        use some::mod2;
15752
15753        const A: u32 = 42;
15754        const B: u32 = 42;
15755        const C: u32 = 42;
15756        ˇ
15757
15758        fn main() {
15759            println!("hello");
15760
15761            println!("world");
15762        }
15763        "#
15764        .unindent(),
15765    );
15766
15767    cx.set_head_text(&diff_base);
15768    executor.run_until_parked();
15769
15770    cx.update_editor(|editor, window, cx| {
15771        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15772    });
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      + ˇ
15784
15785        fn main() {
15786            println!("hello");
15787
15788            println!("world");
15789        }
15790      "#
15791        .unindent(),
15792    );
15793
15794    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
15795    executor.run_until_parked();
15796
15797    cx.assert_state_with_diff(
15798        r#"
15799        use some::mod1;
15800        use some::mod2;
15801
15802        const A: u32 = 42;
15803      + const B: u32 = 42;
15804      + const C: u32 = 42;
15805      + const D: u32 = 42;
15806      + ˇ
15807
15808        fn main() {
15809            println!("hello");
15810
15811            println!("world");
15812        }
15813      "#
15814        .unindent(),
15815    );
15816
15817    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
15818    executor.run_until_parked();
15819
15820    cx.assert_state_with_diff(
15821        r#"
15822        use some::mod1;
15823        use some::mod2;
15824
15825        const A: u32 = 42;
15826      + const B: u32 = 42;
15827      + const C: u32 = 42;
15828      + const D: u32 = 42;
15829      + const E: u32 = 42;
15830      + ˇ
15831
15832        fn main() {
15833            println!("hello");
15834
15835            println!("world");
15836        }
15837      "#
15838        .unindent(),
15839    );
15840
15841    cx.update_editor(|editor, window, cx| {
15842        editor.delete_line(&DeleteLine, window, cx);
15843    });
15844    executor.run_until_parked();
15845
15846    cx.assert_state_with_diff(
15847        r#"
15848        use some::mod1;
15849        use some::mod2;
15850
15851        const A: u32 = 42;
15852      + const B: u32 = 42;
15853      + const C: u32 = 42;
15854      + const D: u32 = 42;
15855      + const E: u32 = 42;
15856        ˇ
15857        fn main() {
15858            println!("hello");
15859
15860            println!("world");
15861        }
15862      "#
15863        .unindent(),
15864    );
15865
15866    cx.update_editor(|editor, window, cx| {
15867        editor.move_up(&MoveUp, window, cx);
15868        editor.delete_line(&DeleteLine, window, cx);
15869        editor.move_up(&MoveUp, window, cx);
15870        editor.delete_line(&DeleteLine, window, cx);
15871        editor.move_up(&MoveUp, window, cx);
15872        editor.delete_line(&DeleteLine, window, cx);
15873    });
15874    executor.run_until_parked();
15875    cx.assert_state_with_diff(
15876        r#"
15877        use some::mod1;
15878        use some::mod2;
15879
15880        const A: u32 = 42;
15881      + const B: u32 = 42;
15882        ˇ
15883        fn main() {
15884            println!("hello");
15885
15886            println!("world");
15887        }
15888      "#
15889        .unindent(),
15890    );
15891
15892    cx.update_editor(|editor, window, cx| {
15893        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
15894        editor.delete_line(&DeleteLine, window, cx);
15895    });
15896    executor.run_until_parked();
15897    cx.assert_state_with_diff(
15898        r#"
15899        ˇ
15900        fn main() {
15901            println!("hello");
15902
15903            println!("world");
15904        }
15905      "#
15906        .unindent(),
15907    );
15908}
15909
15910#[gpui::test]
15911async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
15912    init_test(cx, |_| {});
15913
15914    let mut cx = EditorTestContext::new(cx).await;
15915    cx.set_head_text(indoc! { "
15916        one
15917        two
15918        three
15919        four
15920        five
15921        "
15922    });
15923    cx.set_state(indoc! { "
15924        one
15925        ˇthree
15926        five
15927    "});
15928    cx.run_until_parked();
15929    cx.update_editor(|editor, window, cx| {
15930        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15931    });
15932    cx.assert_state_with_diff(
15933        indoc! { "
15934        one
15935      - two
15936        ˇthree
15937      - four
15938        five
15939    "}
15940        .to_string(),
15941    );
15942    cx.update_editor(|editor, window, cx| {
15943        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15944    });
15945
15946    cx.assert_state_with_diff(
15947        indoc! { "
15948        one
15949        ˇthree
15950        five
15951    "}
15952        .to_string(),
15953    );
15954
15955    cx.set_state(indoc! { "
15956        one
15957        ˇTWO
15958        three
15959        four
15960        five
15961    "});
15962    cx.run_until_parked();
15963    cx.update_editor(|editor, window, cx| {
15964        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15965    });
15966
15967    cx.assert_state_with_diff(
15968        indoc! { "
15969            one
15970          - two
15971          + ˇTWO
15972            three
15973            four
15974            five
15975        "}
15976        .to_string(),
15977    );
15978    cx.update_editor(|editor, window, cx| {
15979        editor.move_up(&Default::default(), window, cx);
15980        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
15981    });
15982    cx.assert_state_with_diff(
15983        indoc! { "
15984            one
15985            ˇTWO
15986            three
15987            four
15988            five
15989        "}
15990        .to_string(),
15991    );
15992}
15993
15994#[gpui::test]
15995async fn test_edits_around_expanded_deletion_hunks(
15996    executor: BackgroundExecutor,
15997    cx: &mut TestAppContext,
15998) {
15999    init_test(cx, |_| {});
16000
16001    let mut cx = EditorTestContext::new(cx).await;
16002
16003    let diff_base = r#"
16004        use some::mod1;
16005        use some::mod2;
16006
16007        const A: u32 = 42;
16008        const B: u32 = 42;
16009        const C: u32 = 42;
16010
16011
16012        fn main() {
16013            println!("hello");
16014
16015            println!("world");
16016        }
16017    "#
16018    .unindent();
16019    executor.run_until_parked();
16020    cx.set_state(
16021        &r#"
16022        use some::mod1;
16023        use some::mod2;
16024
16025        ˇconst B: u32 = 42;
16026        const C: u32 = 42;
16027
16028
16029        fn main() {
16030            println!("hello");
16031
16032            println!("world");
16033        }
16034        "#
16035        .unindent(),
16036    );
16037
16038    cx.set_head_text(&diff_base);
16039    executor.run_until_parked();
16040
16041    cx.update_editor(|editor, window, cx| {
16042        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16043    });
16044    executor.run_until_parked();
16045
16046    cx.assert_state_with_diff(
16047        r#"
16048        use some::mod1;
16049        use some::mod2;
16050
16051      - const A: u32 = 42;
16052        ˇconst B: u32 = 42;
16053        const C: u32 = 42;
16054
16055
16056        fn main() {
16057            println!("hello");
16058
16059            println!("world");
16060        }
16061      "#
16062        .unindent(),
16063    );
16064
16065    cx.update_editor(|editor, window, cx| {
16066        editor.delete_line(&DeleteLine, window, cx);
16067    });
16068    executor.run_until_parked();
16069    cx.assert_state_with_diff(
16070        r#"
16071        use some::mod1;
16072        use some::mod2;
16073
16074      - const A: u32 = 42;
16075      - const B: u32 = 42;
16076        ˇconst C: u32 = 42;
16077
16078
16079        fn main() {
16080            println!("hello");
16081
16082            println!("world");
16083        }
16084      "#
16085        .unindent(),
16086    );
16087
16088    cx.update_editor(|editor, window, cx| {
16089        editor.delete_line(&DeleteLine, window, cx);
16090    });
16091    executor.run_until_parked();
16092    cx.assert_state_with_diff(
16093        r#"
16094        use some::mod1;
16095        use some::mod2;
16096
16097      - const A: u32 = 42;
16098      - const B: u32 = 42;
16099      - const C: u32 = 42;
16100        ˇ
16101
16102        fn main() {
16103            println!("hello");
16104
16105            println!("world");
16106        }
16107      "#
16108        .unindent(),
16109    );
16110
16111    cx.update_editor(|editor, window, cx| {
16112        editor.handle_input("replacement", window, cx);
16113    });
16114    executor.run_until_parked();
16115    cx.assert_state_with_diff(
16116        r#"
16117        use some::mod1;
16118        use some::mod2;
16119
16120      - const A: u32 = 42;
16121      - const B: u32 = 42;
16122      - const C: u32 = 42;
16123      -
16124      + replacementˇ
16125
16126        fn main() {
16127            println!("hello");
16128
16129            println!("world");
16130        }
16131      "#
16132        .unindent(),
16133    );
16134}
16135
16136#[gpui::test]
16137async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16138    init_test(cx, |_| {});
16139
16140    let mut cx = EditorTestContext::new(cx).await;
16141
16142    let base_text = r#"
16143        one
16144        two
16145        three
16146        four
16147        five
16148    "#
16149    .unindent();
16150    executor.run_until_parked();
16151    cx.set_state(
16152        &r#"
16153        one
16154        two
16155        fˇour
16156        five
16157        "#
16158        .unindent(),
16159    );
16160
16161    cx.set_head_text(&base_text);
16162    executor.run_until_parked();
16163
16164    cx.update_editor(|editor, window, cx| {
16165        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16166    });
16167    executor.run_until_parked();
16168
16169    cx.assert_state_with_diff(
16170        r#"
16171          one
16172          two
16173        - three
16174          fˇour
16175          five
16176        "#
16177        .unindent(),
16178    );
16179
16180    cx.update_editor(|editor, window, cx| {
16181        editor.backspace(&Backspace, window, cx);
16182        editor.backspace(&Backspace, window, cx);
16183    });
16184    executor.run_until_parked();
16185    cx.assert_state_with_diff(
16186        r#"
16187          one
16188          two
16189        - threeˇ
16190        - four
16191        + our
16192          five
16193        "#
16194        .unindent(),
16195    );
16196}
16197
16198#[gpui::test]
16199async fn test_edit_after_expanded_modification_hunk(
16200    executor: BackgroundExecutor,
16201    cx: &mut TestAppContext,
16202) {
16203    init_test(cx, |_| {});
16204
16205    let mut cx = EditorTestContext::new(cx).await;
16206
16207    let diff_base = r#"
16208        use some::mod1;
16209        use some::mod2;
16210
16211        const A: u32 = 42;
16212        const B: u32 = 42;
16213        const C: u32 = 42;
16214        const D: u32 = 42;
16215
16216
16217        fn main() {
16218            println!("hello");
16219
16220            println!("world");
16221        }"#
16222    .unindent();
16223
16224    cx.set_state(
16225        &r#"
16226        use some::mod1;
16227        use some::mod2;
16228
16229        const A: u32 = 42;
16230        const B: u32 = 42;
16231        const C: u32 = 43ˇ
16232        const D: u32 = 42;
16233
16234
16235        fn main() {
16236            println!("hello");
16237
16238            println!("world");
16239        }"#
16240        .unindent(),
16241    );
16242
16243    cx.set_head_text(&diff_base);
16244    executor.run_until_parked();
16245    cx.update_editor(|editor, window, cx| {
16246        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16247    });
16248    executor.run_until_parked();
16249
16250    cx.assert_state_with_diff(
16251        r#"
16252        use some::mod1;
16253        use some::mod2;
16254
16255        const A: u32 = 42;
16256        const B: u32 = 42;
16257      - const C: u32 = 42;
16258      + const C: u32 = 43ˇ
16259        const D: u32 = 42;
16260
16261
16262        fn main() {
16263            println!("hello");
16264
16265            println!("world");
16266        }"#
16267        .unindent(),
16268    );
16269
16270    cx.update_editor(|editor, window, cx| {
16271        editor.handle_input("\nnew_line\n", window, cx);
16272    });
16273    executor.run_until_parked();
16274
16275    cx.assert_state_with_diff(
16276        r#"
16277        use some::mod1;
16278        use some::mod2;
16279
16280        const A: u32 = 42;
16281        const B: u32 = 42;
16282      - const C: u32 = 42;
16283      + const C: u32 = 43
16284      + new_line
16285      + ˇ
16286        const D: u32 = 42;
16287
16288
16289        fn main() {
16290            println!("hello");
16291
16292            println!("world");
16293        }"#
16294        .unindent(),
16295    );
16296}
16297
16298#[gpui::test]
16299async fn test_stage_and_unstage_added_file_hunk(
16300    executor: BackgroundExecutor,
16301    cx: &mut TestAppContext,
16302) {
16303    init_test(cx, |_| {});
16304
16305    let mut cx = EditorTestContext::new(cx).await;
16306    cx.update_editor(|editor, _, cx| {
16307        editor.set_expand_all_diff_hunks(cx);
16308    });
16309
16310    let working_copy = r#"
16311            ˇfn main() {
16312                println!("hello, world!");
16313            }
16314        "#
16315    .unindent();
16316
16317    cx.set_state(&working_copy);
16318    executor.run_until_parked();
16319
16320    cx.assert_state_with_diff(
16321        r#"
16322            + ˇfn main() {
16323            +     println!("hello, world!");
16324            + }
16325        "#
16326        .unindent(),
16327    );
16328    cx.assert_index_text(None);
16329
16330    cx.update_editor(|editor, window, cx| {
16331        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16332    });
16333    executor.run_until_parked();
16334    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16335    cx.assert_state_with_diff(
16336        r#"
16337            + ˇfn main() {
16338            +     println!("hello, world!");
16339            + }
16340        "#
16341        .unindent(),
16342    );
16343
16344    cx.update_editor(|editor, window, cx| {
16345        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16346    });
16347    executor.run_until_parked();
16348    cx.assert_index_text(None);
16349}
16350
16351async fn setup_indent_guides_editor(
16352    text: &str,
16353    cx: &mut TestAppContext,
16354) -> (BufferId, EditorTestContext) {
16355    init_test(cx, |_| {});
16356
16357    let mut cx = EditorTestContext::new(cx).await;
16358
16359    let buffer_id = cx.update_editor(|editor, window, cx| {
16360        editor.set_text(text, window, cx);
16361        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16362
16363        buffer_ids[0]
16364    });
16365
16366    (buffer_id, cx)
16367}
16368
16369fn assert_indent_guides(
16370    range: Range<u32>,
16371    expected: Vec<IndentGuide>,
16372    active_indices: Option<Vec<usize>>,
16373    cx: &mut EditorTestContext,
16374) {
16375    let indent_guides = cx.update_editor(|editor, window, cx| {
16376        let snapshot = editor.snapshot(window, cx).display_snapshot;
16377        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16378            editor,
16379            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16380            true,
16381            &snapshot,
16382            cx,
16383        );
16384
16385        indent_guides.sort_by(|a, b| {
16386            a.depth.cmp(&b.depth).then(
16387                a.start_row
16388                    .cmp(&b.start_row)
16389                    .then(a.end_row.cmp(&b.end_row)),
16390            )
16391        });
16392        indent_guides
16393    });
16394
16395    if let Some(expected) = active_indices {
16396        let active_indices = cx.update_editor(|editor, window, cx| {
16397            let snapshot = editor.snapshot(window, cx).display_snapshot;
16398            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16399        });
16400
16401        assert_eq!(
16402            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16403            expected,
16404            "Active indent guide indices do not match"
16405        );
16406    }
16407
16408    assert_eq!(indent_guides, expected, "Indent guides do not match");
16409}
16410
16411fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16412    IndentGuide {
16413        buffer_id,
16414        start_row: MultiBufferRow(start_row),
16415        end_row: MultiBufferRow(end_row),
16416        depth,
16417        tab_size: 4,
16418        settings: IndentGuideSettings {
16419            enabled: true,
16420            line_width: 1,
16421            active_line_width: 1,
16422            ..Default::default()
16423        },
16424    }
16425}
16426
16427#[gpui::test]
16428async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16429    let (buffer_id, mut cx) = setup_indent_guides_editor(
16430        &"
16431    fn main() {
16432        let a = 1;
16433    }"
16434        .unindent(),
16435        cx,
16436    )
16437    .await;
16438
16439    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16440}
16441
16442#[gpui::test]
16443async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16444    let (buffer_id, mut cx) = setup_indent_guides_editor(
16445        &"
16446    fn main() {
16447        let a = 1;
16448        let b = 2;
16449    }"
16450        .unindent(),
16451        cx,
16452    )
16453    .await;
16454
16455    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16456}
16457
16458#[gpui::test]
16459async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16460    let (buffer_id, mut cx) = setup_indent_guides_editor(
16461        &"
16462    fn main() {
16463        let a = 1;
16464        if a == 3 {
16465            let b = 2;
16466        } else {
16467            let c = 3;
16468        }
16469    }"
16470        .unindent(),
16471        cx,
16472    )
16473    .await;
16474
16475    assert_indent_guides(
16476        0..8,
16477        vec![
16478            indent_guide(buffer_id, 1, 6, 0),
16479            indent_guide(buffer_id, 3, 3, 1),
16480            indent_guide(buffer_id, 5, 5, 1),
16481        ],
16482        None,
16483        &mut cx,
16484    );
16485}
16486
16487#[gpui::test]
16488async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16489    let (buffer_id, mut cx) = setup_indent_guides_editor(
16490        &"
16491    fn main() {
16492        let a = 1;
16493            let b = 2;
16494        let c = 3;
16495    }"
16496        .unindent(),
16497        cx,
16498    )
16499    .await;
16500
16501    assert_indent_guides(
16502        0..5,
16503        vec![
16504            indent_guide(buffer_id, 1, 3, 0),
16505            indent_guide(buffer_id, 2, 2, 1),
16506        ],
16507        None,
16508        &mut cx,
16509    );
16510}
16511
16512#[gpui::test]
16513async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16514    let (buffer_id, mut cx) = setup_indent_guides_editor(
16515        &"
16516        fn main() {
16517            let a = 1;
16518
16519            let c = 3;
16520        }"
16521        .unindent(),
16522        cx,
16523    )
16524    .await;
16525
16526    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16527}
16528
16529#[gpui::test]
16530async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16531    let (buffer_id, mut cx) = setup_indent_guides_editor(
16532        &"
16533        fn main() {
16534            let a = 1;
16535
16536            let c = 3;
16537
16538            if a == 3 {
16539                let b = 2;
16540            } else {
16541                let c = 3;
16542            }
16543        }"
16544        .unindent(),
16545        cx,
16546    )
16547    .await;
16548
16549    assert_indent_guides(
16550        0..11,
16551        vec![
16552            indent_guide(buffer_id, 1, 9, 0),
16553            indent_guide(buffer_id, 6, 6, 1),
16554            indent_guide(buffer_id, 8, 8, 1),
16555        ],
16556        None,
16557        &mut cx,
16558    );
16559}
16560
16561#[gpui::test]
16562async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16563    let (buffer_id, mut cx) = setup_indent_guides_editor(
16564        &"
16565        fn main() {
16566            let a = 1;
16567
16568            let c = 3;
16569
16570            if a == 3 {
16571                let b = 2;
16572            } else {
16573                let c = 3;
16574            }
16575        }"
16576        .unindent(),
16577        cx,
16578    )
16579    .await;
16580
16581    assert_indent_guides(
16582        1..11,
16583        vec![
16584            indent_guide(buffer_id, 1, 9, 0),
16585            indent_guide(buffer_id, 6, 6, 1),
16586            indent_guide(buffer_id, 8, 8, 1),
16587        ],
16588        None,
16589        &mut cx,
16590    );
16591}
16592
16593#[gpui::test]
16594async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16595    let (buffer_id, mut cx) = setup_indent_guides_editor(
16596        &"
16597        fn main() {
16598            let a = 1;
16599
16600            let c = 3;
16601
16602            if a == 3 {
16603                let b = 2;
16604            } else {
16605                let c = 3;
16606            }
16607        }"
16608        .unindent(),
16609        cx,
16610    )
16611    .await;
16612
16613    assert_indent_guides(
16614        1..10,
16615        vec![
16616            indent_guide(buffer_id, 1, 9, 0),
16617            indent_guide(buffer_id, 6, 6, 1),
16618            indent_guide(buffer_id, 8, 8, 1),
16619        ],
16620        None,
16621        &mut cx,
16622    );
16623}
16624
16625#[gpui::test]
16626async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16627    let (buffer_id, mut cx) = setup_indent_guides_editor(
16628        &"
16629        block1
16630            block2
16631                block3
16632                    block4
16633            block2
16634        block1
16635        block1"
16636            .unindent(),
16637        cx,
16638    )
16639    .await;
16640
16641    assert_indent_guides(
16642        1..10,
16643        vec![
16644            indent_guide(buffer_id, 1, 4, 0),
16645            indent_guide(buffer_id, 2, 3, 1),
16646            indent_guide(buffer_id, 3, 3, 2),
16647        ],
16648        None,
16649        &mut cx,
16650    );
16651}
16652
16653#[gpui::test]
16654async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16655    let (buffer_id, mut cx) = setup_indent_guides_editor(
16656        &"
16657        block1
16658            block2
16659                block3
16660
16661        block1
16662        block1"
16663            .unindent(),
16664        cx,
16665    )
16666    .await;
16667
16668    assert_indent_guides(
16669        0..6,
16670        vec![
16671            indent_guide(buffer_id, 1, 2, 0),
16672            indent_guide(buffer_id, 2, 2, 1),
16673        ],
16674        None,
16675        &mut cx,
16676    );
16677}
16678
16679#[gpui::test]
16680async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
16681    let (buffer_id, mut cx) = setup_indent_guides_editor(
16682        &"
16683        block1
16684
16685
16686
16687            block2
16688        "
16689        .unindent(),
16690        cx,
16691    )
16692    .await;
16693
16694    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16695}
16696
16697#[gpui::test]
16698async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
16699    let (buffer_id, mut cx) = setup_indent_guides_editor(
16700        &"
16701        def a:
16702        \tb = 3
16703        \tif True:
16704        \t\tc = 4
16705        \t\td = 5
16706        \tprint(b)
16707        "
16708        .unindent(),
16709        cx,
16710    )
16711    .await;
16712
16713    assert_indent_guides(
16714        0..6,
16715        vec![
16716            indent_guide(buffer_id, 1, 5, 0),
16717            indent_guide(buffer_id, 3, 4, 1),
16718        ],
16719        None,
16720        &mut cx,
16721    );
16722}
16723
16724#[gpui::test]
16725async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
16726    let (buffer_id, mut cx) = setup_indent_guides_editor(
16727        &"
16728    fn main() {
16729        let a = 1;
16730    }"
16731        .unindent(),
16732        cx,
16733    )
16734    .await;
16735
16736    cx.update_editor(|editor, window, cx| {
16737        editor.change_selections(None, window, cx, |s| {
16738            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16739        });
16740    });
16741
16742    assert_indent_guides(
16743        0..3,
16744        vec![indent_guide(buffer_id, 1, 1, 0)],
16745        Some(vec![0]),
16746        &mut cx,
16747    );
16748}
16749
16750#[gpui::test]
16751async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
16752    let (buffer_id, mut cx) = setup_indent_guides_editor(
16753        &"
16754    fn main() {
16755        if 1 == 2 {
16756            let a = 1;
16757        }
16758    }"
16759        .unindent(),
16760        cx,
16761    )
16762    .await;
16763
16764    cx.update_editor(|editor, window, cx| {
16765        editor.change_selections(None, window, cx, |s| {
16766            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16767        });
16768    });
16769
16770    assert_indent_guides(
16771        0..4,
16772        vec![
16773            indent_guide(buffer_id, 1, 3, 0),
16774            indent_guide(buffer_id, 2, 2, 1),
16775        ],
16776        Some(vec![1]),
16777        &mut cx,
16778    );
16779
16780    cx.update_editor(|editor, window, cx| {
16781        editor.change_selections(None, window, cx, |s| {
16782            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16783        });
16784    });
16785
16786    assert_indent_guides(
16787        0..4,
16788        vec![
16789            indent_guide(buffer_id, 1, 3, 0),
16790            indent_guide(buffer_id, 2, 2, 1),
16791        ],
16792        Some(vec![1]),
16793        &mut cx,
16794    );
16795
16796    cx.update_editor(|editor, window, cx| {
16797        editor.change_selections(None, window, cx, |s| {
16798            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
16799        });
16800    });
16801
16802    assert_indent_guides(
16803        0..4,
16804        vec![
16805            indent_guide(buffer_id, 1, 3, 0),
16806            indent_guide(buffer_id, 2, 2, 1),
16807        ],
16808        Some(vec![0]),
16809        &mut cx,
16810    );
16811}
16812
16813#[gpui::test]
16814async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
16815    let (buffer_id, mut cx) = setup_indent_guides_editor(
16816        &"
16817    fn main() {
16818        let a = 1;
16819
16820        let b = 2;
16821    }"
16822        .unindent(),
16823        cx,
16824    )
16825    .await;
16826
16827    cx.update_editor(|editor, window, cx| {
16828        editor.change_selections(None, window, cx, |s| {
16829            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
16830        });
16831    });
16832
16833    assert_indent_guides(
16834        0..5,
16835        vec![indent_guide(buffer_id, 1, 3, 0)],
16836        Some(vec![0]),
16837        &mut cx,
16838    );
16839}
16840
16841#[gpui::test]
16842async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
16843    let (buffer_id, mut cx) = setup_indent_guides_editor(
16844        &"
16845    def m:
16846        a = 1
16847        pass"
16848            .unindent(),
16849        cx,
16850    )
16851    .await;
16852
16853    cx.update_editor(|editor, window, cx| {
16854        editor.change_selections(None, window, cx, |s| {
16855            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
16856        });
16857    });
16858
16859    assert_indent_guides(
16860        0..3,
16861        vec![indent_guide(buffer_id, 1, 2, 0)],
16862        Some(vec![0]),
16863        &mut cx,
16864    );
16865}
16866
16867#[gpui::test]
16868async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
16869    init_test(cx, |_| {});
16870    let mut cx = EditorTestContext::new(cx).await;
16871    let text = indoc! {
16872        "
16873        impl A {
16874            fn b() {
16875                0;
16876                3;
16877                5;
16878                6;
16879                7;
16880            }
16881        }
16882        "
16883    };
16884    let base_text = indoc! {
16885        "
16886        impl A {
16887            fn b() {
16888                0;
16889                1;
16890                2;
16891                3;
16892                4;
16893            }
16894            fn c() {
16895                5;
16896                6;
16897                7;
16898            }
16899        }
16900        "
16901    };
16902
16903    cx.update_editor(|editor, window, cx| {
16904        editor.set_text(text, window, cx);
16905
16906        editor.buffer().update(cx, |multibuffer, cx| {
16907            let buffer = multibuffer.as_singleton().unwrap();
16908            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
16909
16910            multibuffer.set_all_diff_hunks_expanded(cx);
16911            multibuffer.add_diff(diff, cx);
16912
16913            buffer.read(cx).remote_id()
16914        })
16915    });
16916    cx.run_until_parked();
16917
16918    cx.assert_state_with_diff(
16919        indoc! { "
16920          impl A {
16921              fn b() {
16922                  0;
16923        -         1;
16924        -         2;
16925                  3;
16926        -         4;
16927        -     }
16928        -     fn c() {
16929                  5;
16930                  6;
16931                  7;
16932              }
16933          }
16934          ˇ"
16935        }
16936        .to_string(),
16937    );
16938
16939    let mut actual_guides = cx.update_editor(|editor, window, cx| {
16940        editor
16941            .snapshot(window, cx)
16942            .buffer_snapshot
16943            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
16944            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
16945            .collect::<Vec<_>>()
16946    });
16947    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
16948    assert_eq!(
16949        actual_guides,
16950        vec![
16951            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
16952            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
16953            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
16954        ]
16955    );
16956}
16957
16958#[gpui::test]
16959async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16960    init_test(cx, |_| {});
16961    let mut cx = EditorTestContext::new(cx).await;
16962
16963    let diff_base = r#"
16964        a
16965        b
16966        c
16967        "#
16968    .unindent();
16969
16970    cx.set_state(
16971        &r#"
16972        ˇA
16973        b
16974        C
16975        "#
16976        .unindent(),
16977    );
16978    cx.set_head_text(&diff_base);
16979    cx.update_editor(|editor, window, cx| {
16980        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16981    });
16982    executor.run_until_parked();
16983
16984    let both_hunks_expanded = r#"
16985        - a
16986        + ˇA
16987          b
16988        - c
16989        + C
16990        "#
16991    .unindent();
16992
16993    cx.assert_state_with_diff(both_hunks_expanded.clone());
16994
16995    let hunk_ranges = cx.update_editor(|editor, window, cx| {
16996        let snapshot = editor.snapshot(window, cx);
16997        let hunks = editor
16998            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16999            .collect::<Vec<_>>();
17000        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17001        let buffer_id = hunks[0].buffer_id;
17002        hunks
17003            .into_iter()
17004            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17005            .collect::<Vec<_>>()
17006    });
17007    assert_eq!(hunk_ranges.len(), 2);
17008
17009    cx.update_editor(|editor, _, cx| {
17010        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17011    });
17012    executor.run_until_parked();
17013
17014    let second_hunk_expanded = r#"
17015          ˇA
17016          b
17017        - c
17018        + C
17019        "#
17020    .unindent();
17021
17022    cx.assert_state_with_diff(second_hunk_expanded);
17023
17024    cx.update_editor(|editor, _, cx| {
17025        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17026    });
17027    executor.run_until_parked();
17028
17029    cx.assert_state_with_diff(both_hunks_expanded.clone());
17030
17031    cx.update_editor(|editor, _, cx| {
17032        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17033    });
17034    executor.run_until_parked();
17035
17036    let first_hunk_expanded = r#"
17037        - a
17038        + ˇA
17039          b
17040          C
17041        "#
17042    .unindent();
17043
17044    cx.assert_state_with_diff(first_hunk_expanded);
17045
17046    cx.update_editor(|editor, _, cx| {
17047        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17048    });
17049    executor.run_until_parked();
17050
17051    cx.assert_state_with_diff(both_hunks_expanded);
17052
17053    cx.set_state(
17054        &r#"
17055        ˇA
17056        b
17057        "#
17058        .unindent(),
17059    );
17060    cx.run_until_parked();
17061
17062    // TODO this cursor position seems bad
17063    cx.assert_state_with_diff(
17064        r#"
17065        - ˇa
17066        + A
17067          b
17068        "#
17069        .unindent(),
17070    );
17071
17072    cx.update_editor(|editor, window, cx| {
17073        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17074    });
17075
17076    cx.assert_state_with_diff(
17077        r#"
17078            - ˇa
17079            + A
17080              b
17081            - c
17082            "#
17083        .unindent(),
17084    );
17085
17086    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17087        let snapshot = editor.snapshot(window, cx);
17088        let hunks = editor
17089            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17090            .collect::<Vec<_>>();
17091        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17092        let buffer_id = hunks[0].buffer_id;
17093        hunks
17094            .into_iter()
17095            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17096            .collect::<Vec<_>>()
17097    });
17098    assert_eq!(hunk_ranges.len(), 2);
17099
17100    cx.update_editor(|editor, _, cx| {
17101        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17102    });
17103    executor.run_until_parked();
17104
17105    cx.assert_state_with_diff(
17106        r#"
17107        - ˇa
17108        + A
17109          b
17110        "#
17111        .unindent(),
17112    );
17113}
17114
17115#[gpui::test]
17116async fn test_toggle_deletion_hunk_at_start_of_file(
17117    executor: BackgroundExecutor,
17118    cx: &mut TestAppContext,
17119) {
17120    init_test(cx, |_| {});
17121    let mut cx = EditorTestContext::new(cx).await;
17122
17123    let diff_base = r#"
17124        a
17125        b
17126        c
17127        "#
17128    .unindent();
17129
17130    cx.set_state(
17131        &r#"
17132        ˇb
17133        c
17134        "#
17135        .unindent(),
17136    );
17137    cx.set_head_text(&diff_base);
17138    cx.update_editor(|editor, window, cx| {
17139        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17140    });
17141    executor.run_until_parked();
17142
17143    let hunk_expanded = r#"
17144        - a
17145          ˇb
17146          c
17147        "#
17148    .unindent();
17149
17150    cx.assert_state_with_diff(hunk_expanded.clone());
17151
17152    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17153        let snapshot = editor.snapshot(window, cx);
17154        let hunks = editor
17155            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17156            .collect::<Vec<_>>();
17157        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17158        let buffer_id = hunks[0].buffer_id;
17159        hunks
17160            .into_iter()
17161            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17162            .collect::<Vec<_>>()
17163    });
17164    assert_eq!(hunk_ranges.len(), 1);
17165
17166    cx.update_editor(|editor, _, cx| {
17167        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17168    });
17169    executor.run_until_parked();
17170
17171    let hunk_collapsed = r#"
17172          ˇb
17173          c
17174        "#
17175    .unindent();
17176
17177    cx.assert_state_with_diff(hunk_collapsed);
17178
17179    cx.update_editor(|editor, _, cx| {
17180        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17181    });
17182    executor.run_until_parked();
17183
17184    cx.assert_state_with_diff(hunk_expanded.clone());
17185}
17186
17187#[gpui::test]
17188async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17189    init_test(cx, |_| {});
17190
17191    let fs = FakeFs::new(cx.executor());
17192    fs.insert_tree(
17193        path!("/test"),
17194        json!({
17195            ".git": {},
17196            "file-1": "ONE\n",
17197            "file-2": "TWO\n",
17198            "file-3": "THREE\n",
17199        }),
17200    )
17201    .await;
17202
17203    fs.set_head_for_repo(
17204        path!("/test/.git").as_ref(),
17205        &[
17206            ("file-1".into(), "one\n".into()),
17207            ("file-2".into(), "two\n".into()),
17208            ("file-3".into(), "three\n".into()),
17209        ],
17210    );
17211
17212    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17213    let mut buffers = vec![];
17214    for i in 1..=3 {
17215        let buffer = project
17216            .update(cx, |project, cx| {
17217                let path = format!(path!("/test/file-{}"), i);
17218                project.open_local_buffer(path, cx)
17219            })
17220            .await
17221            .unwrap();
17222        buffers.push(buffer);
17223    }
17224
17225    let multibuffer = cx.new(|cx| {
17226        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17227        multibuffer.set_all_diff_hunks_expanded(cx);
17228        for buffer in &buffers {
17229            let snapshot = buffer.read(cx).snapshot();
17230            multibuffer.set_excerpts_for_path(
17231                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17232                buffer.clone(),
17233                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17234                DEFAULT_MULTIBUFFER_CONTEXT,
17235                cx,
17236            );
17237        }
17238        multibuffer
17239    });
17240
17241    let editor = cx.add_window(|window, cx| {
17242        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17243    });
17244    cx.run_until_parked();
17245
17246    let snapshot = editor
17247        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17248        .unwrap();
17249    let hunks = snapshot
17250        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17251        .map(|hunk| match hunk {
17252            DisplayDiffHunk::Unfolded {
17253                display_row_range, ..
17254            } => display_row_range,
17255            DisplayDiffHunk::Folded { .. } => unreachable!(),
17256        })
17257        .collect::<Vec<_>>();
17258    assert_eq!(
17259        hunks,
17260        [
17261            DisplayRow(2)..DisplayRow(4),
17262            DisplayRow(7)..DisplayRow(9),
17263            DisplayRow(12)..DisplayRow(14),
17264        ]
17265    );
17266}
17267
17268#[gpui::test]
17269async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17270    init_test(cx, |_| {});
17271
17272    let mut cx = EditorTestContext::new(cx).await;
17273    cx.set_head_text(indoc! { "
17274        one
17275        two
17276        three
17277        four
17278        five
17279        "
17280    });
17281    cx.set_index_text(indoc! { "
17282        one
17283        two
17284        three
17285        four
17286        five
17287        "
17288    });
17289    cx.set_state(indoc! {"
17290        one
17291        TWO
17292        ˇTHREE
17293        FOUR
17294        five
17295    "});
17296    cx.run_until_parked();
17297    cx.update_editor(|editor, window, cx| {
17298        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17299    });
17300    cx.run_until_parked();
17301    cx.assert_index_text(Some(indoc! {"
17302        one
17303        TWO
17304        THREE
17305        FOUR
17306        five
17307    "}));
17308    cx.set_state(indoc! { "
17309        one
17310        TWO
17311        ˇTHREE-HUNDRED
17312        FOUR
17313        five
17314    "});
17315    cx.run_until_parked();
17316    cx.update_editor(|editor, window, cx| {
17317        let snapshot = editor.snapshot(window, cx);
17318        let hunks = editor
17319            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17320            .collect::<Vec<_>>();
17321        assert_eq!(hunks.len(), 1);
17322        assert_eq!(
17323            hunks[0].status(),
17324            DiffHunkStatus {
17325                kind: DiffHunkStatusKind::Modified,
17326                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17327            }
17328        );
17329
17330        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17331    });
17332    cx.run_until_parked();
17333    cx.assert_index_text(Some(indoc! {"
17334        one
17335        TWO
17336        THREE-HUNDRED
17337        FOUR
17338        five
17339    "}));
17340}
17341
17342#[gpui::test]
17343fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17344    init_test(cx, |_| {});
17345
17346    let editor = cx.add_window(|window, cx| {
17347        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17348        build_editor(buffer, window, cx)
17349    });
17350
17351    let render_args = Arc::new(Mutex::new(None));
17352    let snapshot = editor
17353        .update(cx, |editor, window, cx| {
17354            let snapshot = editor.buffer().read(cx).snapshot(cx);
17355            let range =
17356                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17357
17358            struct RenderArgs {
17359                row: MultiBufferRow,
17360                folded: bool,
17361                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17362            }
17363
17364            let crease = Crease::inline(
17365                range,
17366                FoldPlaceholder::test(),
17367                {
17368                    let toggle_callback = render_args.clone();
17369                    move |row, folded, callback, _window, _cx| {
17370                        *toggle_callback.lock() = Some(RenderArgs {
17371                            row,
17372                            folded,
17373                            callback,
17374                        });
17375                        div()
17376                    }
17377                },
17378                |_row, _folded, _window, _cx| div(),
17379            );
17380
17381            editor.insert_creases(Some(crease), cx);
17382            let snapshot = editor.snapshot(window, cx);
17383            let _div = snapshot.render_crease_toggle(
17384                MultiBufferRow(1),
17385                false,
17386                cx.entity().clone(),
17387                window,
17388                cx,
17389            );
17390            snapshot
17391        })
17392        .unwrap();
17393
17394    let render_args = render_args.lock().take().unwrap();
17395    assert_eq!(render_args.row, MultiBufferRow(1));
17396    assert!(!render_args.folded);
17397    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17398
17399    cx.update_window(*editor, |_, window, cx| {
17400        (render_args.callback)(true, window, cx)
17401    })
17402    .unwrap();
17403    let snapshot = editor
17404        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17405        .unwrap();
17406    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17407
17408    cx.update_window(*editor, |_, window, cx| {
17409        (render_args.callback)(false, window, cx)
17410    })
17411    .unwrap();
17412    let snapshot = editor
17413        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17414        .unwrap();
17415    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17416}
17417
17418#[gpui::test]
17419async fn test_input_text(cx: &mut TestAppContext) {
17420    init_test(cx, |_| {});
17421    let mut cx = EditorTestContext::new(cx).await;
17422
17423    cx.set_state(
17424        &r#"ˇone
17425        two
17426
17427        three
17428        fourˇ
17429        five
17430
17431        siˇx"#
17432            .unindent(),
17433    );
17434
17435    cx.dispatch_action(HandleInput(String::new()));
17436    cx.assert_editor_state(
17437        &r#"ˇone
17438        two
17439
17440        three
17441        fourˇ
17442        five
17443
17444        siˇx"#
17445            .unindent(),
17446    );
17447
17448    cx.dispatch_action(HandleInput("AAAA".to_string()));
17449    cx.assert_editor_state(
17450        &r#"AAAAˇone
17451        two
17452
17453        three
17454        fourAAAAˇ
17455        five
17456
17457        siAAAAˇx"#
17458            .unindent(),
17459    );
17460}
17461
17462#[gpui::test]
17463async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17464    init_test(cx, |_| {});
17465
17466    let mut cx = EditorTestContext::new(cx).await;
17467    cx.set_state(
17468        r#"let foo = 1;
17469let foo = 2;
17470let foo = 3;
17471let fooˇ = 4;
17472let foo = 5;
17473let foo = 6;
17474let foo = 7;
17475let foo = 8;
17476let foo = 9;
17477let foo = 10;
17478let foo = 11;
17479let foo = 12;
17480let foo = 13;
17481let foo = 14;
17482let foo = 15;"#,
17483    );
17484
17485    cx.update_editor(|e, window, cx| {
17486        assert_eq!(
17487            e.next_scroll_position,
17488            NextScrollCursorCenterTopBottom::Center,
17489            "Default next scroll direction is center",
17490        );
17491
17492        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17493        assert_eq!(
17494            e.next_scroll_position,
17495            NextScrollCursorCenterTopBottom::Top,
17496            "After center, next scroll direction should be top",
17497        );
17498
17499        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17500        assert_eq!(
17501            e.next_scroll_position,
17502            NextScrollCursorCenterTopBottom::Bottom,
17503            "After top, next scroll direction should be bottom",
17504        );
17505
17506        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17507        assert_eq!(
17508            e.next_scroll_position,
17509            NextScrollCursorCenterTopBottom::Center,
17510            "After bottom, scrolling should start over",
17511        );
17512
17513        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17514        assert_eq!(
17515            e.next_scroll_position,
17516            NextScrollCursorCenterTopBottom::Top,
17517            "Scrolling continues if retriggered fast enough"
17518        );
17519    });
17520
17521    cx.executor()
17522        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17523    cx.executor().run_until_parked();
17524    cx.update_editor(|e, _, _| {
17525        assert_eq!(
17526            e.next_scroll_position,
17527            NextScrollCursorCenterTopBottom::Center,
17528            "If scrolling is not triggered fast enough, it should reset"
17529        );
17530    });
17531}
17532
17533#[gpui::test]
17534async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17535    init_test(cx, |_| {});
17536    let mut cx = EditorLspTestContext::new_rust(
17537        lsp::ServerCapabilities {
17538            definition_provider: Some(lsp::OneOf::Left(true)),
17539            references_provider: Some(lsp::OneOf::Left(true)),
17540            ..lsp::ServerCapabilities::default()
17541        },
17542        cx,
17543    )
17544    .await;
17545
17546    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17547        let go_to_definition = cx
17548            .lsp
17549            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17550                move |params, _| async move {
17551                    if empty_go_to_definition {
17552                        Ok(None)
17553                    } else {
17554                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17555                            uri: params.text_document_position_params.text_document.uri,
17556                            range: lsp::Range::new(
17557                                lsp::Position::new(4, 3),
17558                                lsp::Position::new(4, 6),
17559                            ),
17560                        })))
17561                    }
17562                },
17563            );
17564        let references = cx
17565            .lsp
17566            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17567                Ok(Some(vec![lsp::Location {
17568                    uri: params.text_document_position.text_document.uri,
17569                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17570                }]))
17571            });
17572        (go_to_definition, references)
17573    };
17574
17575    cx.set_state(
17576        &r#"fn one() {
17577            let mut a = ˇtwo();
17578        }
17579
17580        fn two() {}"#
17581            .unindent(),
17582    );
17583    set_up_lsp_handlers(false, &mut cx);
17584    let navigated = cx
17585        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17586        .await
17587        .expect("Failed to navigate to definition");
17588    assert_eq!(
17589        navigated,
17590        Navigated::Yes,
17591        "Should have navigated to definition from the GetDefinition response"
17592    );
17593    cx.assert_editor_state(
17594        &r#"fn one() {
17595            let mut a = two();
17596        }
17597
17598        fn «twoˇ»() {}"#
17599            .unindent(),
17600    );
17601
17602    let editors = cx.update_workspace(|workspace, _, cx| {
17603        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17604    });
17605    cx.update_editor(|_, _, test_editor_cx| {
17606        assert_eq!(
17607            editors.len(),
17608            1,
17609            "Initially, only one, test, editor should be open in the workspace"
17610        );
17611        assert_eq!(
17612            test_editor_cx.entity(),
17613            editors.last().expect("Asserted len is 1").clone()
17614        );
17615    });
17616
17617    set_up_lsp_handlers(true, &mut cx);
17618    let navigated = cx
17619        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17620        .await
17621        .expect("Failed to navigate to lookup references");
17622    assert_eq!(
17623        navigated,
17624        Navigated::Yes,
17625        "Should have navigated to references as a fallback after empty GoToDefinition response"
17626    );
17627    // We should not change the selections in the existing file,
17628    // if opening another milti buffer with the references
17629    cx.assert_editor_state(
17630        &r#"fn one() {
17631            let mut a = two();
17632        }
17633
17634        fn «twoˇ»() {}"#
17635            .unindent(),
17636    );
17637    let editors = cx.update_workspace(|workspace, _, cx| {
17638        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17639    });
17640    cx.update_editor(|_, _, test_editor_cx| {
17641        assert_eq!(
17642            editors.len(),
17643            2,
17644            "After falling back to references search, we open a new editor with the results"
17645        );
17646        let references_fallback_text = editors
17647            .into_iter()
17648            .find(|new_editor| *new_editor != test_editor_cx.entity())
17649            .expect("Should have one non-test editor now")
17650            .read(test_editor_cx)
17651            .text(test_editor_cx);
17652        assert_eq!(
17653            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17654            "Should use the range from the references response and not the GoToDefinition one"
17655        );
17656    });
17657}
17658
17659#[gpui::test]
17660async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
17661    init_test(cx, |_| {});
17662    cx.update(|cx| {
17663        let mut editor_settings = EditorSettings::get_global(cx).clone();
17664        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
17665        EditorSettings::override_global(editor_settings, cx);
17666    });
17667    let mut cx = EditorLspTestContext::new_rust(
17668        lsp::ServerCapabilities {
17669            definition_provider: Some(lsp::OneOf::Left(true)),
17670            references_provider: Some(lsp::OneOf::Left(true)),
17671            ..lsp::ServerCapabilities::default()
17672        },
17673        cx,
17674    )
17675    .await;
17676    let original_state = r#"fn one() {
17677        let mut a = ˇtwo();
17678    }
17679
17680    fn two() {}"#
17681        .unindent();
17682    cx.set_state(&original_state);
17683
17684    let mut go_to_definition = cx
17685        .lsp
17686        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17687            move |_, _| async move { Ok(None) },
17688        );
17689    let _references = cx
17690        .lsp
17691        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
17692            panic!("Should not call for references with no go to definition fallback")
17693        });
17694
17695    let navigated = cx
17696        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17697        .await
17698        .expect("Failed to navigate to lookup references");
17699    go_to_definition
17700        .next()
17701        .await
17702        .expect("Should have called the go_to_definition handler");
17703
17704    assert_eq!(
17705        navigated,
17706        Navigated::No,
17707        "Should have navigated to references as a fallback after empty GoToDefinition response"
17708    );
17709    cx.assert_editor_state(&original_state);
17710    let editors = cx.update_workspace(|workspace, _, cx| {
17711        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17712    });
17713    cx.update_editor(|_, _, _| {
17714        assert_eq!(
17715            editors.len(),
17716            1,
17717            "After unsuccessful fallback, no other editor should have been opened"
17718        );
17719    });
17720}
17721
17722#[gpui::test]
17723async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
17724    init_test(cx, |_| {});
17725
17726    let language = Arc::new(Language::new(
17727        LanguageConfig::default(),
17728        Some(tree_sitter_rust::LANGUAGE.into()),
17729    ));
17730
17731    let text = r#"
17732        #[cfg(test)]
17733        mod tests() {
17734            #[test]
17735            fn runnable_1() {
17736                let a = 1;
17737            }
17738
17739            #[test]
17740            fn runnable_2() {
17741                let a = 1;
17742                let b = 2;
17743            }
17744        }
17745    "#
17746    .unindent();
17747
17748    let fs = FakeFs::new(cx.executor());
17749    fs.insert_file("/file.rs", Default::default()).await;
17750
17751    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17752    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17753    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17754    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17755    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
17756
17757    let editor = cx.new_window_entity(|window, cx| {
17758        Editor::new(
17759            EditorMode::full(),
17760            multi_buffer,
17761            Some(project.clone()),
17762            window,
17763            cx,
17764        )
17765    });
17766
17767    editor.update_in(cx, |editor, window, cx| {
17768        let snapshot = editor.buffer().read(cx).snapshot(cx);
17769        editor.tasks.insert(
17770            (buffer.read(cx).remote_id(), 3),
17771            RunnableTasks {
17772                templates: vec![],
17773                offset: snapshot.anchor_before(43),
17774                column: 0,
17775                extra_variables: HashMap::default(),
17776                context_range: BufferOffset(43)..BufferOffset(85),
17777            },
17778        );
17779        editor.tasks.insert(
17780            (buffer.read(cx).remote_id(), 8),
17781            RunnableTasks {
17782                templates: vec![],
17783                offset: snapshot.anchor_before(86),
17784                column: 0,
17785                extra_variables: HashMap::default(),
17786                context_range: BufferOffset(86)..BufferOffset(191),
17787            },
17788        );
17789
17790        // Test finding task when cursor is inside function body
17791        editor.change_selections(None, window, cx, |s| {
17792            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
17793        });
17794        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17795        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
17796
17797        // Test finding task when cursor is on function name
17798        editor.change_selections(None, window, cx, |s| {
17799            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
17800        });
17801        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
17802        assert_eq!(row, 8, "Should find task when cursor is on function name");
17803    });
17804}
17805
17806#[gpui::test]
17807async fn test_folding_buffers(cx: &mut TestAppContext) {
17808    init_test(cx, |_| {});
17809
17810    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
17811    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
17812    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
17813
17814    let fs = FakeFs::new(cx.executor());
17815    fs.insert_tree(
17816        path!("/a"),
17817        json!({
17818            "first.rs": sample_text_1,
17819            "second.rs": sample_text_2,
17820            "third.rs": sample_text_3,
17821        }),
17822    )
17823    .await;
17824    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17825    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17826    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17827    let worktree = project.update(cx, |project, cx| {
17828        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17829        assert_eq!(worktrees.len(), 1);
17830        worktrees.pop().unwrap()
17831    });
17832    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
17833
17834    let buffer_1 = project
17835        .update(cx, |project, cx| {
17836            project.open_buffer((worktree_id, "first.rs"), cx)
17837        })
17838        .await
17839        .unwrap();
17840    let buffer_2 = project
17841        .update(cx, |project, cx| {
17842            project.open_buffer((worktree_id, "second.rs"), cx)
17843        })
17844        .await
17845        .unwrap();
17846    let buffer_3 = project
17847        .update(cx, |project, cx| {
17848            project.open_buffer((worktree_id, "third.rs"), cx)
17849        })
17850        .await
17851        .unwrap();
17852
17853    let multi_buffer = cx.new(|cx| {
17854        let mut multi_buffer = MultiBuffer::new(ReadWrite);
17855        multi_buffer.push_excerpts(
17856            buffer_1.clone(),
17857            [
17858                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17859                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17860                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17861            ],
17862            cx,
17863        );
17864        multi_buffer.push_excerpts(
17865            buffer_2.clone(),
17866            [
17867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17870            ],
17871            cx,
17872        );
17873        multi_buffer.push_excerpts(
17874            buffer_3.clone(),
17875            [
17876                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17877                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17878                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17879            ],
17880            cx,
17881        );
17882        multi_buffer
17883    });
17884    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17885        Editor::new(
17886            EditorMode::full(),
17887            multi_buffer.clone(),
17888            Some(project.clone()),
17889            window,
17890            cx,
17891        )
17892    });
17893
17894    assert_eq!(
17895        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17896        "\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",
17897    );
17898
17899    multi_buffer_editor.update(cx, |editor, cx| {
17900        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
17901    });
17902    assert_eq!(
17903        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17904        "\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",
17905        "After folding the first buffer, its text should not be displayed"
17906    );
17907
17908    multi_buffer_editor.update(cx, |editor, cx| {
17909        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
17910    });
17911    assert_eq!(
17912        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17913        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
17914        "After folding the second buffer, its text should not be displayed"
17915    );
17916
17917    multi_buffer_editor.update(cx, |editor, cx| {
17918        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
17919    });
17920    assert_eq!(
17921        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17922        "\n\n\n\n\n",
17923        "After folding the third buffer, its text should not be displayed"
17924    );
17925
17926    // Emulate selection inside the fold logic, that should work
17927    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17928        editor
17929            .snapshot(window, cx)
17930            .next_line_boundary(Point::new(0, 4));
17931    });
17932
17933    multi_buffer_editor.update(cx, |editor, cx| {
17934        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
17935    });
17936    assert_eq!(
17937        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17938        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17939        "After unfolding the second buffer, its text should be displayed"
17940    );
17941
17942    // Typing inside of buffer 1 causes that buffer to be unfolded.
17943    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17944        assert_eq!(
17945            multi_buffer
17946                .read(cx)
17947                .snapshot(cx)
17948                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
17949                .collect::<String>(),
17950            "bbbb"
17951        );
17952        editor.change_selections(None, window, cx, |selections| {
17953            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
17954        });
17955        editor.handle_input("B", window, cx);
17956    });
17957
17958    assert_eq!(
17959        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17960        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
17961        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
17962    );
17963
17964    multi_buffer_editor.update(cx, |editor, cx| {
17965        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
17966    });
17967    assert_eq!(
17968        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
17969        "\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",
17970        "After unfolding the all buffers, all original text should be displayed"
17971    );
17972}
17973
17974#[gpui::test]
17975async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
17976    init_test(cx, |_| {});
17977
17978    let sample_text_1 = "1111\n2222\n3333".to_string();
17979    let sample_text_2 = "4444\n5555\n6666".to_string();
17980    let sample_text_3 = "7777\n8888\n9999".to_string();
17981
17982    let fs = FakeFs::new(cx.executor());
17983    fs.insert_tree(
17984        path!("/a"),
17985        json!({
17986            "first.rs": sample_text_1,
17987            "second.rs": sample_text_2,
17988            "third.rs": sample_text_3,
17989        }),
17990    )
17991    .await;
17992    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17993    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17994    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17995    let worktree = project.update(cx, |project, cx| {
17996        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
17997        assert_eq!(worktrees.len(), 1);
17998        worktrees.pop().unwrap()
17999    });
18000    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18001
18002    let buffer_1 = project
18003        .update(cx, |project, cx| {
18004            project.open_buffer((worktree_id, "first.rs"), cx)
18005        })
18006        .await
18007        .unwrap();
18008    let buffer_2 = project
18009        .update(cx, |project, cx| {
18010            project.open_buffer((worktree_id, "second.rs"), cx)
18011        })
18012        .await
18013        .unwrap();
18014    let buffer_3 = project
18015        .update(cx, |project, cx| {
18016            project.open_buffer((worktree_id, "third.rs"), cx)
18017        })
18018        .await
18019        .unwrap();
18020
18021    let multi_buffer = cx.new(|cx| {
18022        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18023        multi_buffer.push_excerpts(
18024            buffer_1.clone(),
18025            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18026            cx,
18027        );
18028        multi_buffer.push_excerpts(
18029            buffer_2.clone(),
18030            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18031            cx,
18032        );
18033        multi_buffer.push_excerpts(
18034            buffer_3.clone(),
18035            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18036            cx,
18037        );
18038        multi_buffer
18039    });
18040
18041    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18042        Editor::new(
18043            EditorMode::full(),
18044            multi_buffer,
18045            Some(project.clone()),
18046            window,
18047            cx,
18048        )
18049    });
18050
18051    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18052    assert_eq!(
18053        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18054        full_text,
18055    );
18056
18057    multi_buffer_editor.update(cx, |editor, cx| {
18058        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18059    });
18060    assert_eq!(
18061        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18062        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18063        "After folding the first buffer, its text should not be displayed"
18064    );
18065
18066    multi_buffer_editor.update(cx, |editor, cx| {
18067        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18068    });
18069
18070    assert_eq!(
18071        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18072        "\n\n\n\n\n\n7777\n8888\n9999",
18073        "After folding the second buffer, its text should not be displayed"
18074    );
18075
18076    multi_buffer_editor.update(cx, |editor, cx| {
18077        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18078    });
18079    assert_eq!(
18080        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18081        "\n\n\n\n\n",
18082        "After folding the third buffer, its text should not be displayed"
18083    );
18084
18085    multi_buffer_editor.update(cx, |editor, cx| {
18086        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18087    });
18088    assert_eq!(
18089        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18090        "\n\n\n\n4444\n5555\n6666\n\n",
18091        "After unfolding the second buffer, its text should be displayed"
18092    );
18093
18094    multi_buffer_editor.update(cx, |editor, cx| {
18095        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18096    });
18097    assert_eq!(
18098        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18099        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18100        "After unfolding the first buffer, its text should be displayed"
18101    );
18102
18103    multi_buffer_editor.update(cx, |editor, cx| {
18104        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18105    });
18106    assert_eq!(
18107        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18108        full_text,
18109        "After unfolding all buffers, all original text should be displayed"
18110    );
18111}
18112
18113#[gpui::test]
18114async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18115    init_test(cx, |_| {});
18116
18117    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18118
18119    let fs = FakeFs::new(cx.executor());
18120    fs.insert_tree(
18121        path!("/a"),
18122        json!({
18123            "main.rs": sample_text,
18124        }),
18125    )
18126    .await;
18127    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18128    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18129    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18130    let worktree = project.update(cx, |project, cx| {
18131        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18132        assert_eq!(worktrees.len(), 1);
18133        worktrees.pop().unwrap()
18134    });
18135    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18136
18137    let buffer_1 = project
18138        .update(cx, |project, cx| {
18139            project.open_buffer((worktree_id, "main.rs"), cx)
18140        })
18141        .await
18142        .unwrap();
18143
18144    let multi_buffer = cx.new(|cx| {
18145        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18146        multi_buffer.push_excerpts(
18147            buffer_1.clone(),
18148            [ExcerptRange::new(
18149                Point::new(0, 0)
18150                    ..Point::new(
18151                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18152                        0,
18153                    ),
18154            )],
18155            cx,
18156        );
18157        multi_buffer
18158    });
18159    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18160        Editor::new(
18161            EditorMode::full(),
18162            multi_buffer,
18163            Some(project.clone()),
18164            window,
18165            cx,
18166        )
18167    });
18168
18169    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18170    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18171        enum TestHighlight {}
18172        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18173        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18174        editor.highlight_text::<TestHighlight>(
18175            vec![highlight_range.clone()],
18176            HighlightStyle::color(Hsla::green()),
18177            cx,
18178        );
18179        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18180    });
18181
18182    let full_text = format!("\n\n{sample_text}");
18183    assert_eq!(
18184        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18185        full_text,
18186    );
18187}
18188
18189#[gpui::test]
18190async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18191    init_test(cx, |_| {});
18192    cx.update(|cx| {
18193        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18194            "keymaps/default-linux.json",
18195            cx,
18196        )
18197        .unwrap();
18198        cx.bind_keys(default_key_bindings);
18199    });
18200
18201    let (editor, cx) = cx.add_window_view(|window, cx| {
18202        let multi_buffer = MultiBuffer::build_multi(
18203            [
18204                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18205                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18206                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18207                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18208            ],
18209            cx,
18210        );
18211        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18212
18213        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18214        // fold all but the second buffer, so that we test navigating between two
18215        // adjacent folded buffers, as well as folded buffers at the start and
18216        // end the multibuffer
18217        editor.fold_buffer(buffer_ids[0], cx);
18218        editor.fold_buffer(buffer_ids[2], cx);
18219        editor.fold_buffer(buffer_ids[3], cx);
18220
18221        editor
18222    });
18223    cx.simulate_resize(size(px(1000.), px(1000.)));
18224
18225    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18226    cx.assert_excerpts_with_selections(indoc! {"
18227        [EXCERPT]
18228        ˇ[FOLDED]
18229        [EXCERPT]
18230        a1
18231        b1
18232        [EXCERPT]
18233        [FOLDED]
18234        [EXCERPT]
18235        [FOLDED]
18236        "
18237    });
18238    cx.simulate_keystroke("down");
18239    cx.assert_excerpts_with_selections(indoc! {"
18240        [EXCERPT]
18241        [FOLDED]
18242        [EXCERPT]
18243        ˇa1
18244        b1
18245        [EXCERPT]
18246        [FOLDED]
18247        [EXCERPT]
18248        [FOLDED]
18249        "
18250    });
18251    cx.simulate_keystroke("down");
18252    cx.assert_excerpts_with_selections(indoc! {"
18253        [EXCERPT]
18254        [FOLDED]
18255        [EXCERPT]
18256        a1
18257        ˇb1
18258        [EXCERPT]
18259        [FOLDED]
18260        [EXCERPT]
18261        [FOLDED]
18262        "
18263    });
18264    cx.simulate_keystroke("down");
18265    cx.assert_excerpts_with_selections(indoc! {"
18266        [EXCERPT]
18267        [FOLDED]
18268        [EXCERPT]
18269        a1
18270        b1
18271        ˇ[EXCERPT]
18272        [FOLDED]
18273        [EXCERPT]
18274        [FOLDED]
18275        "
18276    });
18277    cx.simulate_keystroke("down");
18278    cx.assert_excerpts_with_selections(indoc! {"
18279        [EXCERPT]
18280        [FOLDED]
18281        [EXCERPT]
18282        a1
18283        b1
18284        [EXCERPT]
18285        ˇ[FOLDED]
18286        [EXCERPT]
18287        [FOLDED]
18288        "
18289    });
18290    for _ in 0..5 {
18291        cx.simulate_keystroke("down");
18292        cx.assert_excerpts_with_selections(indoc! {"
18293            [EXCERPT]
18294            [FOLDED]
18295            [EXCERPT]
18296            a1
18297            b1
18298            [EXCERPT]
18299            [FOLDED]
18300            [EXCERPT]
18301            ˇ[FOLDED]
18302            "
18303        });
18304    }
18305
18306    cx.simulate_keystroke("up");
18307    cx.assert_excerpts_with_selections(indoc! {"
18308        [EXCERPT]
18309        [FOLDED]
18310        [EXCERPT]
18311        a1
18312        b1
18313        [EXCERPT]
18314        ˇ[FOLDED]
18315        [EXCERPT]
18316        [FOLDED]
18317        "
18318    });
18319    cx.simulate_keystroke("up");
18320    cx.assert_excerpts_with_selections(indoc! {"
18321        [EXCERPT]
18322        [FOLDED]
18323        [EXCERPT]
18324        a1
18325        b1
18326        ˇ[EXCERPT]
18327        [FOLDED]
18328        [EXCERPT]
18329        [FOLDED]
18330        "
18331    });
18332    cx.simulate_keystroke("up");
18333    cx.assert_excerpts_with_selections(indoc! {"
18334        [EXCERPT]
18335        [FOLDED]
18336        [EXCERPT]
18337        a1
18338        ˇb1
18339        [EXCERPT]
18340        [FOLDED]
18341        [EXCERPT]
18342        [FOLDED]
18343        "
18344    });
18345    cx.simulate_keystroke("up");
18346    cx.assert_excerpts_with_selections(indoc! {"
18347        [EXCERPT]
18348        [FOLDED]
18349        [EXCERPT]
18350        ˇa1
18351        b1
18352        [EXCERPT]
18353        [FOLDED]
18354        [EXCERPT]
18355        [FOLDED]
18356        "
18357    });
18358    for _ in 0..5 {
18359        cx.simulate_keystroke("up");
18360        cx.assert_excerpts_with_selections(indoc! {"
18361            [EXCERPT]
18362            ˇ[FOLDED]
18363            [EXCERPT]
18364            a1
18365            b1
18366            [EXCERPT]
18367            [FOLDED]
18368            [EXCERPT]
18369            [FOLDED]
18370            "
18371        });
18372    }
18373}
18374
18375#[gpui::test]
18376async fn test_inline_completion_text(cx: &mut TestAppContext) {
18377    init_test(cx, |_| {});
18378
18379    // Simple insertion
18380    assert_highlighted_edits(
18381        "Hello, world!",
18382        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18383        true,
18384        cx,
18385        |highlighted_edits, cx| {
18386            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18387            assert_eq!(highlighted_edits.highlights.len(), 1);
18388            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18389            assert_eq!(
18390                highlighted_edits.highlights[0].1.background_color,
18391                Some(cx.theme().status().created_background)
18392            );
18393        },
18394    )
18395    .await;
18396
18397    // Replacement
18398    assert_highlighted_edits(
18399        "This is a test.",
18400        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18401        false,
18402        cx,
18403        |highlighted_edits, cx| {
18404            assert_eq!(highlighted_edits.text, "That is a test.");
18405            assert_eq!(highlighted_edits.highlights.len(), 1);
18406            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18407            assert_eq!(
18408                highlighted_edits.highlights[0].1.background_color,
18409                Some(cx.theme().status().created_background)
18410            );
18411        },
18412    )
18413    .await;
18414
18415    // Multiple edits
18416    assert_highlighted_edits(
18417        "Hello, world!",
18418        vec![
18419            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18420            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18421        ],
18422        false,
18423        cx,
18424        |highlighted_edits, cx| {
18425            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18426            assert_eq!(highlighted_edits.highlights.len(), 2);
18427            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18428            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18429            assert_eq!(
18430                highlighted_edits.highlights[0].1.background_color,
18431                Some(cx.theme().status().created_background)
18432            );
18433            assert_eq!(
18434                highlighted_edits.highlights[1].1.background_color,
18435                Some(cx.theme().status().created_background)
18436            );
18437        },
18438    )
18439    .await;
18440
18441    // Multiple lines with edits
18442    assert_highlighted_edits(
18443        "First line\nSecond line\nThird line\nFourth line",
18444        vec![
18445            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18446            (
18447                Point::new(2, 0)..Point::new(2, 10),
18448                "New third line".to_string(),
18449            ),
18450            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18451        ],
18452        false,
18453        cx,
18454        |highlighted_edits, cx| {
18455            assert_eq!(
18456                highlighted_edits.text,
18457                "Second modified\nNew third line\nFourth updated line"
18458            );
18459            assert_eq!(highlighted_edits.highlights.len(), 3);
18460            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18461            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18462            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18463            for highlight in &highlighted_edits.highlights {
18464                assert_eq!(
18465                    highlight.1.background_color,
18466                    Some(cx.theme().status().created_background)
18467                );
18468            }
18469        },
18470    )
18471    .await;
18472}
18473
18474#[gpui::test]
18475async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18476    init_test(cx, |_| {});
18477
18478    // Deletion
18479    assert_highlighted_edits(
18480        "Hello, world!",
18481        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18482        true,
18483        cx,
18484        |highlighted_edits, cx| {
18485            assert_eq!(highlighted_edits.text, "Hello, world!");
18486            assert_eq!(highlighted_edits.highlights.len(), 1);
18487            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18488            assert_eq!(
18489                highlighted_edits.highlights[0].1.background_color,
18490                Some(cx.theme().status().deleted_background)
18491            );
18492        },
18493    )
18494    .await;
18495
18496    // Insertion
18497    assert_highlighted_edits(
18498        "Hello, world!",
18499        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18500        true,
18501        cx,
18502        |highlighted_edits, cx| {
18503            assert_eq!(highlighted_edits.highlights.len(), 1);
18504            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18505            assert_eq!(
18506                highlighted_edits.highlights[0].1.background_color,
18507                Some(cx.theme().status().created_background)
18508            );
18509        },
18510    )
18511    .await;
18512}
18513
18514async fn assert_highlighted_edits(
18515    text: &str,
18516    edits: Vec<(Range<Point>, String)>,
18517    include_deletions: bool,
18518    cx: &mut TestAppContext,
18519    assertion_fn: impl Fn(HighlightedText, &App),
18520) {
18521    let window = cx.add_window(|window, cx| {
18522        let buffer = MultiBuffer::build_simple(text, cx);
18523        Editor::new(EditorMode::full(), buffer, None, window, cx)
18524    });
18525    let cx = &mut VisualTestContext::from_window(*window, cx);
18526
18527    let (buffer, snapshot) = window
18528        .update(cx, |editor, _window, cx| {
18529            (
18530                editor.buffer().clone(),
18531                editor.buffer().read(cx).snapshot(cx),
18532            )
18533        })
18534        .unwrap();
18535
18536    let edits = edits
18537        .into_iter()
18538        .map(|(range, edit)| {
18539            (
18540                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18541                edit,
18542            )
18543        })
18544        .collect::<Vec<_>>();
18545
18546    let text_anchor_edits = edits
18547        .clone()
18548        .into_iter()
18549        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18550        .collect::<Vec<_>>();
18551
18552    let edit_preview = window
18553        .update(cx, |_, _window, cx| {
18554            buffer
18555                .read(cx)
18556                .as_singleton()
18557                .unwrap()
18558                .read(cx)
18559                .preview_edits(text_anchor_edits.into(), cx)
18560        })
18561        .unwrap()
18562        .await;
18563
18564    cx.update(|_window, cx| {
18565        let highlighted_edits = inline_completion_edit_text(
18566            &snapshot.as_singleton().unwrap().2,
18567            &edits,
18568            &edit_preview,
18569            include_deletions,
18570            cx,
18571        );
18572        assertion_fn(highlighted_edits, cx)
18573    });
18574}
18575
18576#[track_caller]
18577fn assert_breakpoint(
18578    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18579    path: &Arc<Path>,
18580    expected: Vec<(u32, Breakpoint)>,
18581) {
18582    if expected.len() == 0usize {
18583        assert!(!breakpoints.contains_key(path), "{}", path.display());
18584    } else {
18585        let mut breakpoint = breakpoints
18586            .get(path)
18587            .unwrap()
18588            .into_iter()
18589            .map(|breakpoint| {
18590                (
18591                    breakpoint.row,
18592                    Breakpoint {
18593                        message: breakpoint.message.clone(),
18594                        state: breakpoint.state,
18595                        condition: breakpoint.condition.clone(),
18596                        hit_condition: breakpoint.hit_condition.clone(),
18597                    },
18598                )
18599            })
18600            .collect::<Vec<_>>();
18601
18602        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18603
18604        assert_eq!(expected, breakpoint);
18605    }
18606}
18607
18608fn add_log_breakpoint_at_cursor(
18609    editor: &mut Editor,
18610    log_message: &str,
18611    window: &mut Window,
18612    cx: &mut Context<Editor>,
18613) {
18614    let (anchor, bp) = editor
18615        .breakpoints_at_cursors(window, cx)
18616        .first()
18617        .and_then(|(anchor, bp)| {
18618            if let Some(bp) = bp {
18619                Some((*anchor, bp.clone()))
18620            } else {
18621                None
18622            }
18623        })
18624        .unwrap_or_else(|| {
18625            let cursor_position: Point = editor.selections.newest(cx).head();
18626
18627            let breakpoint_position = editor
18628                .snapshot(window, cx)
18629                .display_snapshot
18630                .buffer_snapshot
18631                .anchor_before(Point::new(cursor_position.row, 0));
18632
18633            (breakpoint_position, Breakpoint::new_log(&log_message))
18634        });
18635
18636    editor.edit_breakpoint_at_anchor(
18637        anchor,
18638        bp,
18639        BreakpointEditAction::EditLogMessage(log_message.into()),
18640        cx,
18641    );
18642}
18643
18644#[gpui::test]
18645async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18646    init_test(cx, |_| {});
18647
18648    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18649    let fs = FakeFs::new(cx.executor());
18650    fs.insert_tree(
18651        path!("/a"),
18652        json!({
18653            "main.rs": sample_text,
18654        }),
18655    )
18656    .await;
18657    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18658    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18659    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18660
18661    let fs = FakeFs::new(cx.executor());
18662    fs.insert_tree(
18663        path!("/a"),
18664        json!({
18665            "main.rs": sample_text,
18666        }),
18667    )
18668    .await;
18669    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18670    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18671    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18672    let worktree_id = workspace
18673        .update(cx, |workspace, _window, cx| {
18674            workspace.project().update(cx, |project, cx| {
18675                project.worktrees(cx).next().unwrap().read(cx).id()
18676            })
18677        })
18678        .unwrap();
18679
18680    let buffer = project
18681        .update(cx, |project, cx| {
18682            project.open_buffer((worktree_id, "main.rs"), cx)
18683        })
18684        .await
18685        .unwrap();
18686
18687    let (editor, cx) = cx.add_window_view(|window, cx| {
18688        Editor::new(
18689            EditorMode::full(),
18690            MultiBuffer::build_from_buffer(buffer, cx),
18691            Some(project.clone()),
18692            window,
18693            cx,
18694        )
18695    });
18696
18697    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18698    let abs_path = project.read_with(cx, |project, cx| {
18699        project
18700            .absolute_path(&project_path, cx)
18701            .map(|path_buf| Arc::from(path_buf.to_owned()))
18702            .unwrap()
18703    });
18704
18705    // assert we can add breakpoint on the first line
18706    editor.update_in(cx, |editor, window, cx| {
18707        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18708        editor.move_to_end(&MoveToEnd, window, cx);
18709        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18710    });
18711
18712    let breakpoints = editor.update(cx, |editor, cx| {
18713        editor
18714            .breakpoint_store()
18715            .as_ref()
18716            .unwrap()
18717            .read(cx)
18718            .all_breakpoints(cx)
18719            .clone()
18720    });
18721
18722    assert_eq!(1, breakpoints.len());
18723    assert_breakpoint(
18724        &breakpoints,
18725        &abs_path,
18726        vec![
18727            (0, Breakpoint::new_standard()),
18728            (3, Breakpoint::new_standard()),
18729        ],
18730    );
18731
18732    editor.update_in(cx, |editor, window, cx| {
18733        editor.move_to_beginning(&MoveToBeginning, 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!(1, breakpoints.len());
18748    assert_breakpoint(
18749        &breakpoints,
18750        &abs_path,
18751        vec![(3, Breakpoint::new_standard())],
18752    );
18753
18754    editor.update_in(cx, |editor, window, cx| {
18755        editor.move_to_end(&MoveToEnd, window, cx);
18756        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18757    });
18758
18759    let breakpoints = editor.update(cx, |editor, cx| {
18760        editor
18761            .breakpoint_store()
18762            .as_ref()
18763            .unwrap()
18764            .read(cx)
18765            .all_breakpoints(cx)
18766            .clone()
18767    });
18768
18769    assert_eq!(0, breakpoints.len());
18770    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18771}
18772
18773#[gpui::test]
18774async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
18775    init_test(cx, |_| {});
18776
18777    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18778
18779    let fs = FakeFs::new(cx.executor());
18780    fs.insert_tree(
18781        path!("/a"),
18782        json!({
18783            "main.rs": sample_text,
18784        }),
18785    )
18786    .await;
18787    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18788    let (workspace, cx) =
18789        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
18790
18791    let worktree_id = workspace.update(cx, |workspace, cx| {
18792        workspace.project().update(cx, |project, cx| {
18793            project.worktrees(cx).next().unwrap().read(cx).id()
18794        })
18795    });
18796
18797    let buffer = project
18798        .update(cx, |project, cx| {
18799            project.open_buffer((worktree_id, "main.rs"), cx)
18800        })
18801        .await
18802        .unwrap();
18803
18804    let (editor, cx) = cx.add_window_view(|window, cx| {
18805        Editor::new(
18806            EditorMode::full(),
18807            MultiBuffer::build_from_buffer(buffer, cx),
18808            Some(project.clone()),
18809            window,
18810            cx,
18811        )
18812    });
18813
18814    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18815    let abs_path = project.read_with(cx, |project, cx| {
18816        project
18817            .absolute_path(&project_path, cx)
18818            .map(|path_buf| Arc::from(path_buf.to_owned()))
18819            .unwrap()
18820    });
18821
18822    editor.update_in(cx, |editor, window, cx| {
18823        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18824    });
18825
18826    let breakpoints = editor.update(cx, |editor, cx| {
18827        editor
18828            .breakpoint_store()
18829            .as_ref()
18830            .unwrap()
18831            .read(cx)
18832            .all_breakpoints(cx)
18833            .clone()
18834    });
18835
18836    assert_breakpoint(
18837        &breakpoints,
18838        &abs_path,
18839        vec![(0, Breakpoint::new_log("hello world"))],
18840    );
18841
18842    // Removing a log message from a log breakpoint should remove it
18843    editor.update_in(cx, |editor, window, cx| {
18844        add_log_breakpoint_at_cursor(editor, "", window, cx);
18845    });
18846
18847    let breakpoints = editor.update(cx, |editor, cx| {
18848        editor
18849            .breakpoint_store()
18850            .as_ref()
18851            .unwrap()
18852            .read(cx)
18853            .all_breakpoints(cx)
18854            .clone()
18855    });
18856
18857    assert_breakpoint(&breakpoints, &abs_path, vec![]);
18858
18859    editor.update_in(cx, |editor, window, cx| {
18860        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18861        editor.move_to_end(&MoveToEnd, window, cx);
18862        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
18863        // Not adding a log message to a standard breakpoint shouldn't remove it
18864        add_log_breakpoint_at_cursor(editor, "", window, cx);
18865    });
18866
18867    let breakpoints = editor.update(cx, |editor, cx| {
18868        editor
18869            .breakpoint_store()
18870            .as_ref()
18871            .unwrap()
18872            .read(cx)
18873            .all_breakpoints(cx)
18874            .clone()
18875    });
18876
18877    assert_breakpoint(
18878        &breakpoints,
18879        &abs_path,
18880        vec![
18881            (0, Breakpoint::new_standard()),
18882            (3, Breakpoint::new_standard()),
18883        ],
18884    );
18885
18886    editor.update_in(cx, |editor, window, cx| {
18887        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
18888    });
18889
18890    let breakpoints = editor.update(cx, |editor, cx| {
18891        editor
18892            .breakpoint_store()
18893            .as_ref()
18894            .unwrap()
18895            .read(cx)
18896            .all_breakpoints(cx)
18897            .clone()
18898    });
18899
18900    assert_breakpoint(
18901        &breakpoints,
18902        &abs_path,
18903        vec![
18904            (0, Breakpoint::new_standard()),
18905            (3, Breakpoint::new_log("hello world")),
18906        ],
18907    );
18908
18909    editor.update_in(cx, |editor, window, cx| {
18910        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
18911    });
18912
18913    let breakpoints = editor.update(cx, |editor, cx| {
18914        editor
18915            .breakpoint_store()
18916            .as_ref()
18917            .unwrap()
18918            .read(cx)
18919            .all_breakpoints(cx)
18920            .clone()
18921    });
18922
18923    assert_breakpoint(
18924        &breakpoints,
18925        &abs_path,
18926        vec![
18927            (0, Breakpoint::new_standard()),
18928            (3, Breakpoint::new_log("hello Earth!!")),
18929        ],
18930    );
18931}
18932
18933/// This also tests that Editor::breakpoint_at_cursor_head is working properly
18934/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
18935/// or when breakpoints were placed out of order. This tests for a regression too
18936#[gpui::test]
18937async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
18938    init_test(cx, |_| {});
18939
18940    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18941    let fs = FakeFs::new(cx.executor());
18942    fs.insert_tree(
18943        path!("/a"),
18944        json!({
18945            "main.rs": sample_text,
18946        }),
18947    )
18948    .await;
18949    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18950    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18951    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18952
18953    let fs = FakeFs::new(cx.executor());
18954    fs.insert_tree(
18955        path!("/a"),
18956        json!({
18957            "main.rs": sample_text,
18958        }),
18959    )
18960    .await;
18961    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18962    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18963    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18964    let worktree_id = workspace
18965        .update(cx, |workspace, _window, cx| {
18966            workspace.project().update(cx, |project, cx| {
18967                project.worktrees(cx).next().unwrap().read(cx).id()
18968            })
18969        })
18970        .unwrap();
18971
18972    let buffer = project
18973        .update(cx, |project, cx| {
18974            project.open_buffer((worktree_id, "main.rs"), cx)
18975        })
18976        .await
18977        .unwrap();
18978
18979    let (editor, cx) = cx.add_window_view(|window, cx| {
18980        Editor::new(
18981            EditorMode::full(),
18982            MultiBuffer::build_from_buffer(buffer, cx),
18983            Some(project.clone()),
18984            window,
18985            cx,
18986        )
18987    });
18988
18989    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
18990    let abs_path = project.read_with(cx, |project, cx| {
18991        project
18992            .absolute_path(&project_path, cx)
18993            .map(|path_buf| Arc::from(path_buf.to_owned()))
18994            .unwrap()
18995    });
18996
18997    // assert we can add breakpoint on the first line
18998    editor.update_in(cx, |editor, window, cx| {
18999        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19000        editor.move_to_end(&MoveToEnd, window, cx);
19001        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19002        editor.move_up(&MoveUp, window, cx);
19003        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19004    });
19005
19006    let breakpoints = editor.update(cx, |editor, cx| {
19007        editor
19008            .breakpoint_store()
19009            .as_ref()
19010            .unwrap()
19011            .read(cx)
19012            .all_breakpoints(cx)
19013            .clone()
19014    });
19015
19016    assert_eq!(1, breakpoints.len());
19017    assert_breakpoint(
19018        &breakpoints,
19019        &abs_path,
19020        vec![
19021            (0, Breakpoint::new_standard()),
19022            (2, Breakpoint::new_standard()),
19023            (3, Breakpoint::new_standard()),
19024        ],
19025    );
19026
19027    editor.update_in(cx, |editor, window, cx| {
19028        editor.move_to_beginning(&MoveToBeginning, window, cx);
19029        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19030        editor.move_to_end(&MoveToEnd, window, cx);
19031        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19032        // Disabling a breakpoint that doesn't exist should do nothing
19033        editor.move_up(&MoveUp, window, cx);
19034        editor.move_up(&MoveUp, window, cx);
19035        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19036    });
19037
19038    let breakpoints = editor.update(cx, |editor, cx| {
19039        editor
19040            .breakpoint_store()
19041            .as_ref()
19042            .unwrap()
19043            .read(cx)
19044            .all_breakpoints(cx)
19045            .clone()
19046    });
19047
19048    let disable_breakpoint = {
19049        let mut bp = Breakpoint::new_standard();
19050        bp.state = BreakpointState::Disabled;
19051        bp
19052    };
19053
19054    assert_eq!(1, breakpoints.len());
19055    assert_breakpoint(
19056        &breakpoints,
19057        &abs_path,
19058        vec![
19059            (0, disable_breakpoint.clone()),
19060            (2, Breakpoint::new_standard()),
19061            (3, disable_breakpoint.clone()),
19062        ],
19063    );
19064
19065    editor.update_in(cx, |editor, window, cx| {
19066        editor.move_to_beginning(&MoveToBeginning, window, cx);
19067        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19068        editor.move_to_end(&MoveToEnd, window, cx);
19069        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19070        editor.move_up(&MoveUp, window, cx);
19071        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19072    });
19073
19074    let breakpoints = editor.update(cx, |editor, cx| {
19075        editor
19076            .breakpoint_store()
19077            .as_ref()
19078            .unwrap()
19079            .read(cx)
19080            .all_breakpoints(cx)
19081            .clone()
19082    });
19083
19084    assert_eq!(1, breakpoints.len());
19085    assert_breakpoint(
19086        &breakpoints,
19087        &abs_path,
19088        vec![
19089            (0, Breakpoint::new_standard()),
19090            (2, disable_breakpoint),
19091            (3, Breakpoint::new_standard()),
19092        ],
19093    );
19094}
19095
19096#[gpui::test]
19097async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19098    init_test(cx, |_| {});
19099    let capabilities = lsp::ServerCapabilities {
19100        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19101            prepare_provider: Some(true),
19102            work_done_progress_options: Default::default(),
19103        })),
19104        ..Default::default()
19105    };
19106    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19107
19108    cx.set_state(indoc! {"
19109        struct Fˇoo {}
19110    "});
19111
19112    cx.update_editor(|editor, _, cx| {
19113        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19114        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19115        editor.highlight_background::<DocumentHighlightRead>(
19116            &[highlight_range],
19117            |c| c.editor_document_highlight_read_background,
19118            cx,
19119        );
19120    });
19121
19122    let mut prepare_rename_handler = cx
19123        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19124            move |_, _, _| async move {
19125                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19126                    start: lsp::Position {
19127                        line: 0,
19128                        character: 7,
19129                    },
19130                    end: lsp::Position {
19131                        line: 0,
19132                        character: 10,
19133                    },
19134                })))
19135            },
19136        );
19137    let prepare_rename_task = cx
19138        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19139        .expect("Prepare rename was not started");
19140    prepare_rename_handler.next().await.unwrap();
19141    prepare_rename_task.await.expect("Prepare rename failed");
19142
19143    let mut rename_handler =
19144        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19145            let edit = lsp::TextEdit {
19146                range: lsp::Range {
19147                    start: lsp::Position {
19148                        line: 0,
19149                        character: 7,
19150                    },
19151                    end: lsp::Position {
19152                        line: 0,
19153                        character: 10,
19154                    },
19155                },
19156                new_text: "FooRenamed".to_string(),
19157            };
19158            Ok(Some(lsp::WorkspaceEdit::new(
19159                // Specify the same edit twice
19160                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19161            )))
19162        });
19163    let rename_task = cx
19164        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19165        .expect("Confirm rename was not started");
19166    rename_handler.next().await.unwrap();
19167    rename_task.await.expect("Confirm rename failed");
19168    cx.run_until_parked();
19169
19170    // Despite two edits, only one is actually applied as those are identical
19171    cx.assert_editor_state(indoc! {"
19172        struct FooRenamedˇ {}
19173    "});
19174}
19175
19176#[gpui::test]
19177async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19178    init_test(cx, |_| {});
19179    // These capabilities indicate that the server does not support prepare rename.
19180    let capabilities = lsp::ServerCapabilities {
19181        rename_provider: Some(lsp::OneOf::Left(true)),
19182        ..Default::default()
19183    };
19184    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19185
19186    cx.set_state(indoc! {"
19187        struct Fˇoo {}
19188    "});
19189
19190    cx.update_editor(|editor, _window, cx| {
19191        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19192        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19193        editor.highlight_background::<DocumentHighlightRead>(
19194            &[highlight_range],
19195            |c| c.editor_document_highlight_read_background,
19196            cx,
19197        );
19198    });
19199
19200    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19201        .expect("Prepare rename was not started")
19202        .await
19203        .expect("Prepare rename failed");
19204
19205    let mut rename_handler =
19206        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19207            let edit = lsp::TextEdit {
19208                range: lsp::Range {
19209                    start: lsp::Position {
19210                        line: 0,
19211                        character: 7,
19212                    },
19213                    end: lsp::Position {
19214                        line: 0,
19215                        character: 10,
19216                    },
19217                },
19218                new_text: "FooRenamed".to_string(),
19219            };
19220            Ok(Some(lsp::WorkspaceEdit::new(
19221                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19222            )))
19223        });
19224    let rename_task = cx
19225        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19226        .expect("Confirm rename was not started");
19227    rename_handler.next().await.unwrap();
19228    rename_task.await.expect("Confirm rename failed");
19229    cx.run_until_parked();
19230
19231    // Correct range is renamed, as `surrounding_word` is used to find it.
19232    cx.assert_editor_state(indoc! {"
19233        struct FooRenamedˇ {}
19234    "});
19235}
19236
19237#[gpui::test]
19238async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19239    init_test(cx, |_| {});
19240    let mut cx = EditorTestContext::new(cx).await;
19241
19242    let language = Arc::new(
19243        Language::new(
19244            LanguageConfig::default(),
19245            Some(tree_sitter_html::LANGUAGE.into()),
19246        )
19247        .with_brackets_query(
19248            r#"
19249            ("<" @open "/>" @close)
19250            ("</" @open ">" @close)
19251            ("<" @open ">" @close)
19252            ("\"" @open "\"" @close)
19253            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19254        "#,
19255        )
19256        .unwrap(),
19257    );
19258    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19259
19260    cx.set_state(indoc! {"
19261        <span>ˇ</span>
19262    "});
19263    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19264    cx.assert_editor_state(indoc! {"
19265        <span>
19266        ˇ
19267        </span>
19268    "});
19269
19270    cx.set_state(indoc! {"
19271        <span><span></span>ˇ</span>
19272    "});
19273    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19274    cx.assert_editor_state(indoc! {"
19275        <span><span></span>
19276        ˇ</span>
19277    "});
19278
19279    cx.set_state(indoc! {"
19280        <span>ˇ
19281        </span>
19282    "});
19283    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19284    cx.assert_editor_state(indoc! {"
19285        <span>
19286        ˇ
19287        </span>
19288    "});
19289}
19290
19291#[gpui::test(iterations = 10)]
19292async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19293    init_test(cx, |_| {});
19294
19295    let fs = FakeFs::new(cx.executor());
19296    fs.insert_tree(
19297        path!("/dir"),
19298        json!({
19299            "a.ts": "a",
19300        }),
19301    )
19302    .await;
19303
19304    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19305    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19306    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19307
19308    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19309    language_registry.add(Arc::new(Language::new(
19310        LanguageConfig {
19311            name: "TypeScript".into(),
19312            matcher: LanguageMatcher {
19313                path_suffixes: vec!["ts".to_string()],
19314                ..Default::default()
19315            },
19316            ..Default::default()
19317        },
19318        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19319    )));
19320    let mut fake_language_servers = language_registry.register_fake_lsp(
19321        "TypeScript",
19322        FakeLspAdapter {
19323            capabilities: lsp::ServerCapabilities {
19324                code_lens_provider: Some(lsp::CodeLensOptions {
19325                    resolve_provider: Some(true),
19326                }),
19327                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19328                    commands: vec!["_the/command".to_string()],
19329                    ..lsp::ExecuteCommandOptions::default()
19330                }),
19331                ..lsp::ServerCapabilities::default()
19332            },
19333            ..FakeLspAdapter::default()
19334        },
19335    );
19336
19337    let (buffer, _handle) = project
19338        .update(cx, |p, cx| {
19339            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19340        })
19341        .await
19342        .unwrap();
19343    cx.executor().run_until_parked();
19344
19345    let fake_server = fake_language_servers.next().await.unwrap();
19346
19347    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19348    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19349    drop(buffer_snapshot);
19350    let actions = cx
19351        .update_window(*workspace, |_, window, cx| {
19352            project.code_actions(&buffer, anchor..anchor, window, cx)
19353        })
19354        .unwrap();
19355
19356    fake_server
19357        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19358            Ok(Some(vec![
19359                lsp::CodeLens {
19360                    range: lsp::Range::default(),
19361                    command: Some(lsp::Command {
19362                        title: "Code lens command".to_owned(),
19363                        command: "_the/command".to_owned(),
19364                        arguments: None,
19365                    }),
19366                    data: None,
19367                },
19368                lsp::CodeLens {
19369                    range: lsp::Range::default(),
19370                    command: Some(lsp::Command {
19371                        title: "Command not in capabilities".to_owned(),
19372                        command: "not in capabilities".to_owned(),
19373                        arguments: None,
19374                    }),
19375                    data: None,
19376                },
19377                lsp::CodeLens {
19378                    range: lsp::Range {
19379                        start: lsp::Position {
19380                            line: 1,
19381                            character: 1,
19382                        },
19383                        end: lsp::Position {
19384                            line: 1,
19385                            character: 1,
19386                        },
19387                    },
19388                    command: Some(lsp::Command {
19389                        title: "Command not in range".to_owned(),
19390                        command: "_the/command".to_owned(),
19391                        arguments: None,
19392                    }),
19393                    data: None,
19394                },
19395            ]))
19396        })
19397        .next()
19398        .await;
19399
19400    let actions = actions.await.unwrap();
19401    assert_eq!(
19402        actions.len(),
19403        1,
19404        "Should have only one valid action for the 0..0 range"
19405    );
19406    let action = actions[0].clone();
19407    let apply = project.update(cx, |project, cx| {
19408        project.apply_code_action(buffer.clone(), action, true, cx)
19409    });
19410
19411    // Resolving the code action does not populate its edits. In absence of
19412    // edits, we must execute the given command.
19413    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19414        |mut lens, _| async move {
19415            let lens_command = lens.command.as_mut().expect("should have a command");
19416            assert_eq!(lens_command.title, "Code lens command");
19417            lens_command.arguments = Some(vec![json!("the-argument")]);
19418            Ok(lens)
19419        },
19420    );
19421
19422    // While executing the command, the language server sends the editor
19423    // a `workspaceEdit` request.
19424    fake_server
19425        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19426            let fake = fake_server.clone();
19427            move |params, _| {
19428                assert_eq!(params.command, "_the/command");
19429                let fake = fake.clone();
19430                async move {
19431                    fake.server
19432                        .request::<lsp::request::ApplyWorkspaceEdit>(
19433                            lsp::ApplyWorkspaceEditParams {
19434                                label: None,
19435                                edit: lsp::WorkspaceEdit {
19436                                    changes: Some(
19437                                        [(
19438                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19439                                            vec![lsp::TextEdit {
19440                                                range: lsp::Range::new(
19441                                                    lsp::Position::new(0, 0),
19442                                                    lsp::Position::new(0, 0),
19443                                                ),
19444                                                new_text: "X".into(),
19445                                            }],
19446                                        )]
19447                                        .into_iter()
19448                                        .collect(),
19449                                    ),
19450                                    ..Default::default()
19451                                },
19452                            },
19453                        )
19454                        .await
19455                        .into_response()
19456                        .unwrap();
19457                    Ok(Some(json!(null)))
19458                }
19459            }
19460        })
19461        .next()
19462        .await;
19463
19464    // Applying the code lens command returns a project transaction containing the edits
19465    // sent by the language server in its `workspaceEdit` request.
19466    let transaction = apply.await.unwrap();
19467    assert!(transaction.0.contains_key(&buffer));
19468    buffer.update(cx, |buffer, cx| {
19469        assert_eq!(buffer.text(), "Xa");
19470        buffer.undo(cx);
19471        assert_eq!(buffer.text(), "a");
19472    });
19473}
19474
19475#[gpui::test]
19476async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19477    init_test(cx, |_| {});
19478
19479    let fs = FakeFs::new(cx.executor());
19480    let main_text = r#"fn main() {
19481println!("1");
19482println!("2");
19483println!("3");
19484println!("4");
19485println!("5");
19486}"#;
19487    let lib_text = "mod foo {}";
19488    fs.insert_tree(
19489        path!("/a"),
19490        json!({
19491            "lib.rs": lib_text,
19492            "main.rs": main_text,
19493        }),
19494    )
19495    .await;
19496
19497    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19498    let (workspace, cx) =
19499        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19500    let worktree_id = workspace.update(cx, |workspace, cx| {
19501        workspace.project().update(cx, |project, cx| {
19502            project.worktrees(cx).next().unwrap().read(cx).id()
19503        })
19504    });
19505
19506    let expected_ranges = vec![
19507        Point::new(0, 0)..Point::new(0, 0),
19508        Point::new(1, 0)..Point::new(1, 1),
19509        Point::new(2, 0)..Point::new(2, 2),
19510        Point::new(3, 0)..Point::new(3, 3),
19511    ];
19512
19513    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19514    let editor_1 = workspace
19515        .update_in(cx, |workspace, window, cx| {
19516            workspace.open_path(
19517                (worktree_id, "main.rs"),
19518                Some(pane_1.downgrade()),
19519                true,
19520                window,
19521                cx,
19522            )
19523        })
19524        .unwrap()
19525        .await
19526        .downcast::<Editor>()
19527        .unwrap();
19528    pane_1.update(cx, |pane, cx| {
19529        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19530        open_editor.update(cx, |editor, cx| {
19531            assert_eq!(
19532                editor.display_text(cx),
19533                main_text,
19534                "Original main.rs text on initial open",
19535            );
19536            assert_eq!(
19537                editor
19538                    .selections
19539                    .all::<Point>(cx)
19540                    .into_iter()
19541                    .map(|s| s.range())
19542                    .collect::<Vec<_>>(),
19543                vec![Point::zero()..Point::zero()],
19544                "Default selections on initial open",
19545            );
19546        })
19547    });
19548    editor_1.update_in(cx, |editor, window, cx| {
19549        editor.change_selections(None, window, cx, |s| {
19550            s.select_ranges(expected_ranges.clone());
19551        });
19552    });
19553
19554    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19555        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19556    });
19557    let editor_2 = workspace
19558        .update_in(cx, |workspace, window, cx| {
19559            workspace.open_path(
19560                (worktree_id, "main.rs"),
19561                Some(pane_2.downgrade()),
19562                true,
19563                window,
19564                cx,
19565            )
19566        })
19567        .unwrap()
19568        .await
19569        .downcast::<Editor>()
19570        .unwrap();
19571    pane_2.update(cx, |pane, cx| {
19572        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19573        open_editor.update(cx, |editor, cx| {
19574            assert_eq!(
19575                editor.display_text(cx),
19576                main_text,
19577                "Original main.rs text on initial open in another panel",
19578            );
19579            assert_eq!(
19580                editor
19581                    .selections
19582                    .all::<Point>(cx)
19583                    .into_iter()
19584                    .map(|s| s.range())
19585                    .collect::<Vec<_>>(),
19586                vec![Point::zero()..Point::zero()],
19587                "Default selections on initial open in another panel",
19588            );
19589        })
19590    });
19591
19592    editor_2.update_in(cx, |editor, window, cx| {
19593        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19594    });
19595
19596    let _other_editor_1 = workspace
19597        .update_in(cx, |workspace, window, cx| {
19598            workspace.open_path(
19599                (worktree_id, "lib.rs"),
19600                Some(pane_1.downgrade()),
19601                true,
19602                window,
19603                cx,
19604            )
19605        })
19606        .unwrap()
19607        .await
19608        .downcast::<Editor>()
19609        .unwrap();
19610    pane_1
19611        .update_in(cx, |pane, window, cx| {
19612            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19613                .unwrap()
19614        })
19615        .await
19616        .unwrap();
19617    drop(editor_1);
19618    pane_1.update(cx, |pane, cx| {
19619        pane.active_item()
19620            .unwrap()
19621            .downcast::<Editor>()
19622            .unwrap()
19623            .update(cx, |editor, cx| {
19624                assert_eq!(
19625                    editor.display_text(cx),
19626                    lib_text,
19627                    "Other file should be open and active",
19628                );
19629            });
19630        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19631    });
19632
19633    let _other_editor_2 = workspace
19634        .update_in(cx, |workspace, window, cx| {
19635            workspace.open_path(
19636                (worktree_id, "lib.rs"),
19637                Some(pane_2.downgrade()),
19638                true,
19639                window,
19640                cx,
19641            )
19642        })
19643        .unwrap()
19644        .await
19645        .downcast::<Editor>()
19646        .unwrap();
19647    pane_2
19648        .update_in(cx, |pane, window, cx| {
19649            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19650                .unwrap()
19651        })
19652        .await
19653        .unwrap();
19654    drop(editor_2);
19655    pane_2.update(cx, |pane, cx| {
19656        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19657        open_editor.update(cx, |editor, cx| {
19658            assert_eq!(
19659                editor.display_text(cx),
19660                lib_text,
19661                "Other file should be open and active in another panel too",
19662            );
19663        });
19664        assert_eq!(
19665            pane.items().count(),
19666            1,
19667            "No other editors should be open in another pane",
19668        );
19669    });
19670
19671    let _editor_1_reopened = workspace
19672        .update_in(cx, |workspace, window, cx| {
19673            workspace.open_path(
19674                (worktree_id, "main.rs"),
19675                Some(pane_1.downgrade()),
19676                true,
19677                window,
19678                cx,
19679            )
19680        })
19681        .unwrap()
19682        .await
19683        .downcast::<Editor>()
19684        .unwrap();
19685    let _editor_2_reopened = workspace
19686        .update_in(cx, |workspace, window, cx| {
19687            workspace.open_path(
19688                (worktree_id, "main.rs"),
19689                Some(pane_2.downgrade()),
19690                true,
19691                window,
19692                cx,
19693            )
19694        })
19695        .unwrap()
19696        .await
19697        .downcast::<Editor>()
19698        .unwrap();
19699    pane_1.update(cx, |pane, cx| {
19700        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19701        open_editor.update(cx, |editor, cx| {
19702            assert_eq!(
19703                editor.display_text(cx),
19704                main_text,
19705                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
19706            );
19707            assert_eq!(
19708                editor
19709                    .selections
19710                    .all::<Point>(cx)
19711                    .into_iter()
19712                    .map(|s| s.range())
19713                    .collect::<Vec<_>>(),
19714                expected_ranges,
19715                "Previous editor in the 1st panel had selections and should get them restored on reopen",
19716            );
19717        })
19718    });
19719    pane_2.update(cx, |pane, cx| {
19720        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19721        open_editor.update(cx, |editor, cx| {
19722            assert_eq!(
19723                editor.display_text(cx),
19724                r#"fn main() {
19725⋯rintln!("1");
19726⋯intln!("2");
19727⋯ntln!("3");
19728println!("4");
19729println!("5");
19730}"#,
19731                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
19732            );
19733            assert_eq!(
19734                editor
19735                    .selections
19736                    .all::<Point>(cx)
19737                    .into_iter()
19738                    .map(|s| s.range())
19739                    .collect::<Vec<_>>(),
19740                vec![Point::zero()..Point::zero()],
19741                "Previous editor in the 2nd pane had no selections changed hence should restore none",
19742            );
19743        })
19744    });
19745}
19746
19747#[gpui::test]
19748async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
19749    init_test(cx, |_| {});
19750
19751    let fs = FakeFs::new(cx.executor());
19752    let main_text = r#"fn main() {
19753println!("1");
19754println!("2");
19755println!("3");
19756println!("4");
19757println!("5");
19758}"#;
19759    let lib_text = "mod foo {}";
19760    fs.insert_tree(
19761        path!("/a"),
19762        json!({
19763            "lib.rs": lib_text,
19764            "main.rs": main_text,
19765        }),
19766    )
19767    .await;
19768
19769    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19770    let (workspace, cx) =
19771        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19772    let worktree_id = workspace.update(cx, |workspace, cx| {
19773        workspace.project().update(cx, |project, cx| {
19774            project.worktrees(cx).next().unwrap().read(cx).id()
19775        })
19776    });
19777
19778    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19779    let editor = workspace
19780        .update_in(cx, |workspace, window, cx| {
19781            workspace.open_path(
19782                (worktree_id, "main.rs"),
19783                Some(pane.downgrade()),
19784                true,
19785                window,
19786                cx,
19787            )
19788        })
19789        .unwrap()
19790        .await
19791        .downcast::<Editor>()
19792        .unwrap();
19793    pane.update(cx, |pane, cx| {
19794        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19795        open_editor.update(cx, |editor, cx| {
19796            assert_eq!(
19797                editor.display_text(cx),
19798                main_text,
19799                "Original main.rs text on initial open",
19800            );
19801        })
19802    });
19803    editor.update_in(cx, |editor, window, cx| {
19804        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
19805    });
19806
19807    cx.update_global(|store: &mut SettingsStore, cx| {
19808        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19809            s.restore_on_file_reopen = Some(false);
19810        });
19811    });
19812    editor.update_in(cx, |editor, window, cx| {
19813        editor.fold_ranges(
19814            vec![
19815                Point::new(1, 0)..Point::new(1, 1),
19816                Point::new(2, 0)..Point::new(2, 2),
19817                Point::new(3, 0)..Point::new(3, 3),
19818            ],
19819            false,
19820            window,
19821            cx,
19822        );
19823    });
19824    pane.update_in(cx, |pane, window, cx| {
19825        pane.close_all_items(&CloseAllItems::default(), window, cx)
19826            .unwrap()
19827    })
19828    .await
19829    .unwrap();
19830    pane.update(cx, |pane, _| {
19831        assert!(pane.active_item().is_none());
19832    });
19833    cx.update_global(|store: &mut SettingsStore, cx| {
19834        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
19835            s.restore_on_file_reopen = Some(true);
19836        });
19837    });
19838
19839    let _editor_reopened = workspace
19840        .update_in(cx, |workspace, window, cx| {
19841            workspace.open_path(
19842                (worktree_id, "main.rs"),
19843                Some(pane.downgrade()),
19844                true,
19845                window,
19846                cx,
19847            )
19848        })
19849        .unwrap()
19850        .await
19851        .downcast::<Editor>()
19852        .unwrap();
19853    pane.update(cx, |pane, cx| {
19854        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19855        open_editor.update(cx, |editor, cx| {
19856            assert_eq!(
19857                editor.display_text(cx),
19858                main_text,
19859                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
19860            );
19861        })
19862    });
19863}
19864
19865#[gpui::test]
19866async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
19867    struct EmptyModalView {
19868        focus_handle: gpui::FocusHandle,
19869    }
19870    impl EventEmitter<DismissEvent> for EmptyModalView {}
19871    impl Render for EmptyModalView {
19872        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
19873            div()
19874        }
19875    }
19876    impl Focusable for EmptyModalView {
19877        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
19878            self.focus_handle.clone()
19879        }
19880    }
19881    impl workspace::ModalView for EmptyModalView {}
19882    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
19883        EmptyModalView {
19884            focus_handle: cx.focus_handle(),
19885        }
19886    }
19887
19888    init_test(cx, |_| {});
19889
19890    let fs = FakeFs::new(cx.executor());
19891    let project = Project::test(fs, [], cx).await;
19892    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19893    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
19894    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19895    let editor = cx.new_window_entity(|window, cx| {
19896        Editor::new(
19897            EditorMode::full(),
19898            buffer,
19899            Some(project.clone()),
19900            window,
19901            cx,
19902        )
19903    });
19904    workspace
19905        .update(cx, |workspace, window, cx| {
19906            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
19907        })
19908        .unwrap();
19909    editor.update_in(cx, |editor, window, cx| {
19910        editor.open_context_menu(&OpenContextMenu, window, cx);
19911        assert!(editor.mouse_context_menu.is_some());
19912    });
19913    workspace
19914        .update(cx, |workspace, window, cx| {
19915            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
19916        })
19917        .unwrap();
19918    cx.read(|cx| {
19919        assert!(editor.read(cx).mouse_context_menu.is_none());
19920    });
19921}
19922
19923#[gpui::test]
19924async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
19925    init_test(cx, |_| {});
19926
19927    let fs = FakeFs::new(cx.executor());
19928    fs.insert_file(path!("/file.html"), Default::default())
19929        .await;
19930
19931    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19932
19933    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19934    let html_language = Arc::new(Language::new(
19935        LanguageConfig {
19936            name: "HTML".into(),
19937            matcher: LanguageMatcher {
19938                path_suffixes: vec!["html".to_string()],
19939                ..LanguageMatcher::default()
19940            },
19941            brackets: BracketPairConfig {
19942                pairs: vec![BracketPair {
19943                    start: "<".into(),
19944                    end: ">".into(),
19945                    close: true,
19946                    ..Default::default()
19947                }],
19948                ..Default::default()
19949            },
19950            ..Default::default()
19951        },
19952        Some(tree_sitter_html::LANGUAGE.into()),
19953    ));
19954    language_registry.add(html_language);
19955    let mut fake_servers = language_registry.register_fake_lsp(
19956        "HTML",
19957        FakeLspAdapter {
19958            capabilities: lsp::ServerCapabilities {
19959                completion_provider: Some(lsp::CompletionOptions {
19960                    resolve_provider: Some(true),
19961                    ..Default::default()
19962                }),
19963                ..Default::default()
19964            },
19965            ..Default::default()
19966        },
19967    );
19968
19969    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19970    let cx = &mut VisualTestContext::from_window(*workspace, cx);
19971
19972    let worktree_id = workspace
19973        .update(cx, |workspace, _window, cx| {
19974            workspace.project().update(cx, |project, cx| {
19975                project.worktrees(cx).next().unwrap().read(cx).id()
19976            })
19977        })
19978        .unwrap();
19979    project
19980        .update(cx, |project, cx| {
19981            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
19982        })
19983        .await
19984        .unwrap();
19985    let editor = workspace
19986        .update(cx, |workspace, window, cx| {
19987            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
19988        })
19989        .unwrap()
19990        .await
19991        .unwrap()
19992        .downcast::<Editor>()
19993        .unwrap();
19994
19995    let fake_server = fake_servers.next().await.unwrap();
19996    editor.update_in(cx, |editor, window, cx| {
19997        editor.set_text("<ad></ad>", window, cx);
19998        editor.change_selections(None, window, cx, |selections| {
19999            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20000        });
20001        let Some((buffer, _)) = editor
20002            .buffer
20003            .read(cx)
20004            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20005        else {
20006            panic!("Failed to get buffer for selection position");
20007        };
20008        let buffer = buffer.read(cx);
20009        let buffer_id = buffer.remote_id();
20010        let opening_range =
20011            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20012        let closing_range =
20013            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20014        let mut linked_ranges = HashMap::default();
20015        linked_ranges.insert(
20016            buffer_id,
20017            vec![(opening_range.clone(), vec![closing_range.clone()])],
20018        );
20019        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20020    });
20021    let mut completion_handle =
20022        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20023            Ok(Some(lsp::CompletionResponse::Array(vec![
20024                lsp::CompletionItem {
20025                    label: "head".to_string(),
20026                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20027                        lsp::InsertReplaceEdit {
20028                            new_text: "head".to_string(),
20029                            insert: lsp::Range::new(
20030                                lsp::Position::new(0, 1),
20031                                lsp::Position::new(0, 3),
20032                            ),
20033                            replace: lsp::Range::new(
20034                                lsp::Position::new(0, 1),
20035                                lsp::Position::new(0, 3),
20036                            ),
20037                        },
20038                    )),
20039                    ..Default::default()
20040                },
20041            ])))
20042        });
20043    editor.update_in(cx, |editor, window, cx| {
20044        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20045    });
20046    cx.run_until_parked();
20047    completion_handle.next().await.unwrap();
20048    editor.update(cx, |editor, _| {
20049        assert!(
20050            editor.context_menu_visible(),
20051            "Completion menu should be visible"
20052        );
20053    });
20054    editor.update_in(cx, |editor, window, cx| {
20055        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20056    });
20057    cx.executor().run_until_parked();
20058    editor.update(cx, |editor, cx| {
20059        assert_eq!(editor.text(cx), "<head></head>");
20060    });
20061}
20062
20063#[gpui::test]
20064async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20065    init_test(cx, |_| {});
20066
20067    let fs = FakeFs::new(cx.executor());
20068    fs.insert_tree(
20069        path!("/root"),
20070        json!({
20071            "a": {
20072                "main.rs": "fn main() {}",
20073            },
20074            "foo": {
20075                "bar": {
20076                    "external_file.rs": "pub mod external {}",
20077                }
20078            }
20079        }),
20080    )
20081    .await;
20082
20083    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20085    language_registry.add(rust_lang());
20086    let _fake_servers = language_registry.register_fake_lsp(
20087        "Rust",
20088        FakeLspAdapter {
20089            ..FakeLspAdapter::default()
20090        },
20091    );
20092    let (workspace, cx) =
20093        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20094    let worktree_id = workspace.update(cx, |workspace, cx| {
20095        workspace.project().update(cx, |project, cx| {
20096            project.worktrees(cx).next().unwrap().read(cx).id()
20097        })
20098    });
20099
20100    let assert_language_servers_count =
20101        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20102            project.update(cx, |project, cx| {
20103                let current = project
20104                    .lsp_store()
20105                    .read(cx)
20106                    .as_local()
20107                    .unwrap()
20108                    .language_servers
20109                    .len();
20110                assert_eq!(expected, current, "{context}");
20111            });
20112        };
20113
20114    assert_language_servers_count(
20115        0,
20116        "No servers should be running before any file is open",
20117        cx,
20118    );
20119    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20120    let main_editor = workspace
20121        .update_in(cx, |workspace, window, cx| {
20122            workspace.open_path(
20123                (worktree_id, "main.rs"),
20124                Some(pane.downgrade()),
20125                true,
20126                window,
20127                cx,
20128            )
20129        })
20130        .unwrap()
20131        .await
20132        .downcast::<Editor>()
20133        .unwrap();
20134    pane.update(cx, |pane, cx| {
20135        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20136        open_editor.update(cx, |editor, cx| {
20137            assert_eq!(
20138                editor.display_text(cx),
20139                "fn main() {}",
20140                "Original main.rs text on initial open",
20141            );
20142        });
20143        assert_eq!(open_editor, main_editor);
20144    });
20145    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20146
20147    let external_editor = workspace
20148        .update_in(cx, |workspace, window, cx| {
20149            workspace.open_abs_path(
20150                PathBuf::from("/root/foo/bar/external_file.rs"),
20151                OpenOptions::default(),
20152                window,
20153                cx,
20154            )
20155        })
20156        .await
20157        .expect("opening external file")
20158        .downcast::<Editor>()
20159        .expect("downcasted external file's open element to editor");
20160    pane.update(cx, |pane, cx| {
20161        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20162        open_editor.update(cx, |editor, cx| {
20163            assert_eq!(
20164                editor.display_text(cx),
20165                "pub mod external {}",
20166                "External file is open now",
20167            );
20168        });
20169        assert_eq!(open_editor, external_editor);
20170    });
20171    assert_language_servers_count(
20172        1,
20173        "Second, external, *.rs file should join the existing server",
20174        cx,
20175    );
20176
20177    pane.update_in(cx, |pane, window, cx| {
20178        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20179    })
20180    .unwrap()
20181    .await
20182    .unwrap();
20183    pane.update_in(cx, |pane, window, cx| {
20184        pane.navigate_backward(window, cx);
20185    });
20186    cx.run_until_parked();
20187    pane.update(cx, |pane, cx| {
20188        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20189        open_editor.update(cx, |editor, cx| {
20190            assert_eq!(
20191                editor.display_text(cx),
20192                "pub mod external {}",
20193                "External file is open now",
20194            );
20195        });
20196    });
20197    assert_language_servers_count(
20198        1,
20199        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20200        cx,
20201    );
20202
20203    cx.update(|_, cx| {
20204        workspace::reload(&workspace::Reload::default(), cx);
20205    });
20206    assert_language_servers_count(
20207        1,
20208        "After reloading the worktree with local and external files opened, only one project should be started",
20209        cx,
20210    );
20211}
20212
20213fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
20214    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
20215    point..point
20216}
20217
20218fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
20219    let (text, ranges) = marked_text_ranges(marked_text, true);
20220    assert_eq!(editor.text(cx), text);
20221    assert_eq!(
20222        editor.selections.ranges(cx),
20223        ranges,
20224        "Assert selections are {}",
20225        marked_text
20226    );
20227}
20228
20229pub fn handle_signature_help_request(
20230    cx: &mut EditorLspTestContext,
20231    mocked_response: lsp::SignatureHelp,
20232) -> impl Future<Output = ()> + use<> {
20233    let mut request =
20234        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
20235            let mocked_response = mocked_response.clone();
20236            async move { Ok(Some(mocked_response)) }
20237        });
20238
20239    async move {
20240        request.next().await;
20241    }
20242}
20243
20244/// Handle completion request passing a marked string specifying where the completion
20245/// should be triggered from using '|' character, what range should be replaced, and what completions
20246/// should be returned using '<' and '>' to delimit the range.
20247///
20248/// Also see `handle_completion_request_with_insert_and_replace`.
20249#[track_caller]
20250pub fn handle_completion_request(
20251    cx: &mut EditorLspTestContext,
20252    marked_string: &str,
20253    completions: Vec<&'static str>,
20254    counter: Arc<AtomicUsize>,
20255) -> impl Future<Output = ()> {
20256    let complete_from_marker: TextRangeMarker = '|'.into();
20257    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20258    let (_, mut marked_ranges) = marked_text_ranges_by(
20259        marked_string,
20260        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20261    );
20262
20263    let complete_from_position =
20264        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20265    let replace_range =
20266        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20267
20268    let mut request =
20269        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20270            let completions = completions.clone();
20271            counter.fetch_add(1, atomic::Ordering::Release);
20272            async move {
20273                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20274                assert_eq!(
20275                    params.text_document_position.position,
20276                    complete_from_position
20277                );
20278                Ok(Some(lsp::CompletionResponse::Array(
20279                    completions
20280                        .iter()
20281                        .map(|completion_text| lsp::CompletionItem {
20282                            label: completion_text.to_string(),
20283                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20284                                range: replace_range,
20285                                new_text: completion_text.to_string(),
20286                            })),
20287                            ..Default::default()
20288                        })
20289                        .collect(),
20290                )))
20291            }
20292        });
20293
20294    async move {
20295        request.next().await;
20296    }
20297}
20298
20299/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
20300/// given instead, which also contains an `insert` range.
20301///
20302/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
20303/// that is, `replace_range.start..cursor_pos`.
20304pub fn handle_completion_request_with_insert_and_replace(
20305    cx: &mut EditorLspTestContext,
20306    marked_string: &str,
20307    completions: Vec<&'static str>,
20308    counter: Arc<AtomicUsize>,
20309) -> impl Future<Output = ()> {
20310    let complete_from_marker: TextRangeMarker = '|'.into();
20311    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20312    let (_, mut marked_ranges) = marked_text_ranges_by(
20313        marked_string,
20314        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20315    );
20316
20317    let complete_from_position =
20318        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20319    let replace_range =
20320        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20321
20322    let mut request =
20323        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20324            let completions = completions.clone();
20325            counter.fetch_add(1, atomic::Ordering::Release);
20326            async move {
20327                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20328                assert_eq!(
20329                    params.text_document_position.position, complete_from_position,
20330                    "marker `|` position doesn't match",
20331                );
20332                Ok(Some(lsp::CompletionResponse::Array(
20333                    completions
20334                        .iter()
20335                        .map(|completion_text| lsp::CompletionItem {
20336                            label: completion_text.to_string(),
20337                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20338                                lsp::InsertReplaceEdit {
20339                                    insert: lsp::Range {
20340                                        start: replace_range.start,
20341                                        end: complete_from_position,
20342                                    },
20343                                    replace: replace_range,
20344                                    new_text: completion_text.to_string(),
20345                                },
20346                            )),
20347                            ..Default::default()
20348                        })
20349                        .collect(),
20350                )))
20351            }
20352        });
20353
20354    async move {
20355        request.next().await;
20356    }
20357}
20358
20359fn handle_resolve_completion_request(
20360    cx: &mut EditorLspTestContext,
20361    edits: Option<Vec<(&'static str, &'static str)>>,
20362) -> impl Future<Output = ()> {
20363    let edits = edits.map(|edits| {
20364        edits
20365            .iter()
20366            .map(|(marked_string, new_text)| {
20367                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
20368                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
20369                lsp::TextEdit::new(replace_range, new_text.to_string())
20370            })
20371            .collect::<Vec<_>>()
20372    });
20373
20374    let mut request =
20375        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20376            let edits = edits.clone();
20377            async move {
20378                Ok(lsp::CompletionItem {
20379                    additional_text_edits: edits,
20380                    ..Default::default()
20381                })
20382            }
20383        });
20384
20385    async move {
20386        request.next().await;
20387    }
20388}
20389
20390pub(crate) fn update_test_language_settings(
20391    cx: &mut TestAppContext,
20392    f: impl Fn(&mut AllLanguageSettingsContent),
20393) {
20394    cx.update(|cx| {
20395        SettingsStore::update_global(cx, |store, cx| {
20396            store.update_user_settings::<AllLanguageSettings>(cx, f);
20397        });
20398    });
20399}
20400
20401pub(crate) fn update_test_project_settings(
20402    cx: &mut TestAppContext,
20403    f: impl Fn(&mut ProjectSettings),
20404) {
20405    cx.update(|cx| {
20406        SettingsStore::update_global(cx, |store, cx| {
20407            store.update_user_settings::<ProjectSettings>(cx, f);
20408        });
20409    });
20410}
20411
20412pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
20413    cx.update(|cx| {
20414        assets::Assets.load_test_fonts(cx);
20415        let store = SettingsStore::test(cx);
20416        cx.set_global(store);
20417        theme::init(theme::LoadThemes::JustBase, cx);
20418        release_channel::init(SemanticVersion::default(), cx);
20419        client::init_settings(cx);
20420        language::init(cx);
20421        Project::init_settings(cx);
20422        workspace::init_settings(cx);
20423        crate::init(cx);
20424    });
20425
20426    update_test_language_settings(cx, f);
20427}
20428
20429#[track_caller]
20430fn assert_hunk_revert(
20431    not_reverted_text_with_selections: &str,
20432    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
20433    expected_reverted_text_with_selections: &str,
20434    base_text: &str,
20435    cx: &mut EditorLspTestContext,
20436) {
20437    cx.set_state(not_reverted_text_with_selections);
20438    cx.set_head_text(base_text);
20439    cx.executor().run_until_parked();
20440
20441    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
20442        let snapshot = editor.snapshot(window, cx);
20443        let reverted_hunk_statuses = snapshot
20444            .buffer_snapshot
20445            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
20446            .map(|hunk| hunk.status().kind)
20447            .collect::<Vec<_>>();
20448
20449        editor.git_restore(&Default::default(), window, cx);
20450        reverted_hunk_statuses
20451    });
20452    cx.executor().run_until_parked();
20453    cx.assert_editor_state(expected_reverted_text_with_selections);
20454    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
20455}