editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    inline_completion_tests::FakeInlineCompletionProvider,
    5    linked_editing_ranges::LinkedEditingRanges,
    6    scroll::scroll_amount::ScrollAmount,
    7    test::{
    8        assert_text_with_selections, build_editor,
    9        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   10        editor_test_context::EditorTestContext,
   11        select_ranges,
   12    },
   13};
   14use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   15use futures::StreamExt;
   16use gpui::{
   17    BackgroundExecutor, DismissEvent, SemanticVersion, TestAppContext, UpdateGlobal,
   18    VisualTestContext, WindowBounds, WindowOptions, div,
   19};
   20use indoc::indoc;
   21use language::{
   22    BracketPairConfig,
   23    Capability::ReadWrite,
   24    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   25    Override, Point,
   26    language_settings::{
   27        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   28        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   29    },
   30    tree_sitter_python,
   31};
   32use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   33use lsp::CompletionParams;
   34use multi_buffer::{IndentGuide, PathKey};
   35use parking_lot::Mutex;
   36use pretty_assertions::{assert_eq, assert_ne};
   37use project::{
   38    FakeFs,
   39    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   40    project_settings::{LspSettings, ProjectSettings},
   41};
   42use serde_json::{self, json};
   43use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   44use std::{
   45    iter,
   46    sync::atomic::{self, AtomicUsize},
   47};
   48use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   49use text::ToPoint as _;
   50use unindent::Unindent;
   51use util::{
   52    assert_set_eq, path,
   53    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   54    uri,
   55};
   56use workspace::{
   57    CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
   58    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   59};
   60
   61#[gpui::test]
   62fn test_edit_events(cx: &mut TestAppContext) {
   63    init_test(cx, |_| {});
   64
   65    let buffer = cx.new(|cx| {
   66        let mut buffer = language::Buffer::local("123456", cx);
   67        buffer.set_group_interval(Duration::from_secs(1));
   68        buffer
   69    });
   70
   71    let events = Rc::new(RefCell::new(Vec::new()));
   72    let editor1 = cx.add_window({
   73        let events = events.clone();
   74        |window, cx| {
   75            let entity = cx.entity().clone();
   76            cx.subscribe_in(
   77                &entity,
   78                window,
   79                move |_, _, event: &EditorEvent, _, _| match event {
   80                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   81                    EditorEvent::BufferEdited => {
   82                        events.borrow_mut().push(("editor1", "buffer edited"))
   83                    }
   84                    _ => {}
   85                },
   86            )
   87            .detach();
   88            Editor::for_buffer(buffer.clone(), None, window, cx)
   89        }
   90    });
   91
   92    let editor2 = cx.add_window({
   93        let events = events.clone();
   94        |window, cx| {
   95            cx.subscribe_in(
   96                &cx.entity().clone(),
   97                window,
   98                move |_, _, event: &EditorEvent, _, _| match event {
   99                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  100                    EditorEvent::BufferEdited => {
  101                        events.borrow_mut().push(("editor2", "buffer edited"))
  102                    }
  103                    _ => {}
  104                },
  105            )
  106            .detach();
  107            Editor::for_buffer(buffer.clone(), None, window, cx)
  108        }
  109    });
  110
  111    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  112
  113    // Mutating editor 1 will emit an `Edited` event only for that editor.
  114    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  115    assert_eq!(
  116        mem::take(&mut *events.borrow_mut()),
  117        [
  118            ("editor1", "edited"),
  119            ("editor1", "buffer edited"),
  120            ("editor2", "buffer edited"),
  121        ]
  122    );
  123
  124    // Mutating editor 2 will emit an `Edited` event only for that editor.
  125    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  126    assert_eq!(
  127        mem::take(&mut *events.borrow_mut()),
  128        [
  129            ("editor2", "edited"),
  130            ("editor1", "buffer edited"),
  131            ("editor2", "buffer edited"),
  132        ]
  133    );
  134
  135    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  136    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  137    assert_eq!(
  138        mem::take(&mut *events.borrow_mut()),
  139        [
  140            ("editor1", "edited"),
  141            ("editor1", "buffer edited"),
  142            ("editor2", "buffer edited"),
  143        ]
  144    );
  145
  146    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  147    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  148    assert_eq!(
  149        mem::take(&mut *events.borrow_mut()),
  150        [
  151            ("editor1", "edited"),
  152            ("editor1", "buffer edited"),
  153            ("editor2", "buffer edited"),
  154        ]
  155    );
  156
  157    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  158    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  159    assert_eq!(
  160        mem::take(&mut *events.borrow_mut()),
  161        [
  162            ("editor2", "edited"),
  163            ("editor1", "buffer edited"),
  164            ("editor2", "buffer edited"),
  165        ]
  166    );
  167
  168    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  169    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  170    assert_eq!(
  171        mem::take(&mut *events.borrow_mut()),
  172        [
  173            ("editor2", "edited"),
  174            ("editor1", "buffer edited"),
  175            ("editor2", "buffer edited"),
  176        ]
  177    );
  178
  179    // No event is emitted when the mutation is a no-op.
  180    _ = editor2.update(cx, |editor, window, cx| {
  181        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  182
  183        editor.backspace(&Backspace, window, cx);
  184    });
  185    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  186}
  187
  188#[gpui::test]
  189fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  190    init_test(cx, |_| {});
  191
  192    let mut now = Instant::now();
  193    let group_interval = Duration::from_millis(1);
  194    let buffer = cx.new(|cx| {
  195        let mut buf = language::Buffer::local("123456", cx);
  196        buf.set_group_interval(group_interval);
  197        buf
  198    });
  199    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  200    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  201
  202    _ = editor.update(cx, |editor, window, cx| {
  203        editor.start_transaction_at(now, window, cx);
  204        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  205
  206        editor.insert("cd", window, cx);
  207        editor.end_transaction_at(now, cx);
  208        assert_eq!(editor.text(cx), "12cd56");
  209        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  210
  211        editor.start_transaction_at(now, window, cx);
  212        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  213        editor.insert("e", window, cx);
  214        editor.end_transaction_at(now, cx);
  215        assert_eq!(editor.text(cx), "12cde6");
  216        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  217
  218        now += group_interval + Duration::from_millis(1);
  219        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  220
  221        // Simulate an edit in another editor
  222        buffer.update(cx, |buffer, cx| {
  223            buffer.start_transaction_at(now, cx);
  224            buffer.edit([(0..1, "a")], None, cx);
  225            buffer.edit([(1..1, "b")], None, cx);
  226            buffer.end_transaction_at(now, cx);
  227        });
  228
  229        assert_eq!(editor.text(cx), "ab2cde6");
  230        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  231
  232        // Last transaction happened past the group interval in a different editor.
  233        // Undo it individually and don't restore selections.
  234        editor.undo(&Undo, window, cx);
  235        assert_eq!(editor.text(cx), "12cde6");
  236        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  237
  238        // First two transactions happened within the group interval in this editor.
  239        // Undo them together and restore selections.
  240        editor.undo(&Undo, window, cx);
  241        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  242        assert_eq!(editor.text(cx), "123456");
  243        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  244
  245        // Redo the first two transactions together.
  246        editor.redo(&Redo, window, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  249
  250        // Redo the last transaction on its own.
  251        editor.redo(&Redo, window, cx);
  252        assert_eq!(editor.text(cx), "ab2cde6");
  253        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  254
  255        // Test empty transactions.
  256        editor.start_transaction_at(now, window, cx);
  257        editor.end_transaction_at(now, cx);
  258        editor.undo(&Undo, window, cx);
  259        assert_eq!(editor.text(cx), "12cde6");
  260    });
  261}
  262
  263#[gpui::test]
  264fn test_ime_composition(cx: &mut TestAppContext) {
  265    init_test(cx, |_| {});
  266
  267    let buffer = cx.new(|cx| {
  268        let mut buffer = language::Buffer::local("abcde", cx);
  269        // Ensure automatic grouping doesn't occur.
  270        buffer.set_group_interval(Duration::ZERO);
  271        buffer
  272    });
  273
  274    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  275    cx.add_window(|window, cx| {
  276        let mut editor = build_editor(buffer.clone(), window, cx);
  277
  278        // Start a new IME composition.
  279        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  280        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  281        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  282        assert_eq!(editor.text(cx), "äbcde");
  283        assert_eq!(
  284            editor.marked_text_ranges(cx),
  285            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  286        );
  287
  288        // Finalize IME composition.
  289        editor.replace_text_in_range(None, "ā", window, cx);
  290        assert_eq!(editor.text(cx), "ābcde");
  291        assert_eq!(editor.marked_text_ranges(cx), None);
  292
  293        // IME composition edits are grouped and are undone/redone at once.
  294        editor.undo(&Default::default(), window, cx);
  295        assert_eq!(editor.text(cx), "abcde");
  296        assert_eq!(editor.marked_text_ranges(cx), None);
  297        editor.redo(&Default::default(), window, cx);
  298        assert_eq!(editor.text(cx), "ābcde");
  299        assert_eq!(editor.marked_text_ranges(cx), None);
  300
  301        // Start a new IME composition.
  302        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  303        assert_eq!(
  304            editor.marked_text_ranges(cx),
  305            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  306        );
  307
  308        // Undoing during an IME composition cancels it.
  309        editor.undo(&Default::default(), window, cx);
  310        assert_eq!(editor.text(cx), "ābcde");
  311        assert_eq!(editor.marked_text_ranges(cx), None);
  312
  313        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  314        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  315        assert_eq!(editor.text(cx), "ābcdè");
  316        assert_eq!(
  317            editor.marked_text_ranges(cx),
  318            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  319        );
  320
  321        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  322        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  323        assert_eq!(editor.text(cx), "ābcdę");
  324        assert_eq!(editor.marked_text_ranges(cx), None);
  325
  326        // Start a new IME composition with multiple cursors.
  327        editor.change_selections(None, window, cx, |s| {
  328            s.select_ranges([
  329                OffsetUtf16(1)..OffsetUtf16(1),
  330                OffsetUtf16(3)..OffsetUtf16(3),
  331                OffsetUtf16(5)..OffsetUtf16(5),
  332            ])
  333        });
  334        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  335        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  336        assert_eq!(
  337            editor.marked_text_ranges(cx),
  338            Some(vec![
  339                OffsetUtf16(0)..OffsetUtf16(3),
  340                OffsetUtf16(4)..OffsetUtf16(7),
  341                OffsetUtf16(8)..OffsetUtf16(11)
  342            ])
  343        );
  344
  345        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  346        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  347        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![
  351                OffsetUtf16(1)..OffsetUtf16(2),
  352                OffsetUtf16(5)..OffsetUtf16(6),
  353                OffsetUtf16(9)..OffsetUtf16(10)
  354            ])
  355        );
  356
  357        // Finalize IME composition with multiple cursors.
  358        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  359        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  360        assert_eq!(editor.marked_text_ranges(cx), None);
  361
  362        editor
  363    });
  364}
  365
  366#[gpui::test]
  367fn test_selection_with_mouse(cx: &mut TestAppContext) {
  368    init_test(cx, |_| {});
  369
  370    let editor = cx.add_window(|window, cx| {
  371        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  372        build_editor(buffer, window, cx)
  373    });
  374
  375    _ = editor.update(cx, |editor, window, cx| {
  376        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  377    });
  378    assert_eq!(
  379        editor
  380            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  381            .unwrap(),
  382        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  383    );
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.update_selection(
  387            DisplayPoint::new(DisplayRow(3), 3),
  388            0,
  389            gpui::Point::<f32>::default(),
  390            window,
  391            cx,
  392        );
  393    });
  394
  395    assert_eq!(
  396        editor
  397            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  398            .unwrap(),
  399        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  400    );
  401
  402    _ = editor.update(cx, |editor, window, cx| {
  403        editor.update_selection(
  404            DisplayPoint::new(DisplayRow(1), 1),
  405            0,
  406            gpui::Point::<f32>::default(),
  407            window,
  408            cx,
  409        );
  410    });
  411
  412    assert_eq!(
  413        editor
  414            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  415            .unwrap(),
  416        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  417    );
  418
  419    _ = editor.update(cx, |editor, window, cx| {
  420        editor.end_selection(window, cx);
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(3), 3),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(0), 0),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [
  453            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  454            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  455        ]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460    });
  461
  462    assert_eq!(
  463        editor
  464            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  465            .unwrap(),
  466        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  467    );
  468}
  469
  470#[gpui::test]
  471fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  472    init_test(cx, |_| {});
  473
  474    let editor = cx.add_window(|window, cx| {
  475        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  476        build_editor(buffer, window, cx)
  477    });
  478
  479    _ = editor.update(cx, |editor, window, cx| {
  480        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  481    });
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485    });
  486
  487    _ = editor.update(cx, |editor, window, cx| {
  488        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  489    });
  490
  491    _ = editor.update(cx, |editor, window, cx| {
  492        editor.end_selection(window, cx);
  493    });
  494
  495    assert_eq!(
  496        editor
  497            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  498            .unwrap(),
  499        [
  500            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  501            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  502        ]
  503    );
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  518    );
  519}
  520
  521#[gpui::test]
  522fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  523    init_test(cx, |_| {});
  524
  525    let editor = cx.add_window(|window, cx| {
  526        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  527        build_editor(buffer, window, cx)
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  532        assert_eq!(
  533            editor.selections.display_ranges(cx),
  534            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  535        );
  536    });
  537
  538    _ = editor.update(cx, |editor, window, cx| {
  539        editor.update_selection(
  540            DisplayPoint::new(DisplayRow(3), 3),
  541            0,
  542            gpui::Point::<f32>::default(),
  543            window,
  544            cx,
  545        );
  546        assert_eq!(
  547            editor.selections.display_ranges(cx),
  548            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  549        );
  550    });
  551
  552    _ = editor.update(cx, |editor, window, cx| {
  553        editor.cancel(&Cancel, window, cx);
  554        editor.update_selection(
  555            DisplayPoint::new(DisplayRow(1), 1),
  556            0,
  557            gpui::Point::<f32>::default(),
  558            window,
  559            cx,
  560        );
  561        assert_eq!(
  562            editor.selections.display_ranges(cx),
  563            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  564        );
  565    });
  566}
  567
  568#[gpui::test]
  569fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  570    init_test(cx, |_| {});
  571
  572    let editor = cx.add_window(|window, cx| {
  573        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  574        build_editor(buffer, window, cx)
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  582        );
  583
  584        editor.move_down(&Default::default(), window, cx);
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  588        );
  589
  590        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  591        assert_eq!(
  592            editor.selections.display_ranges(cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  594        );
  595
  596        editor.move_up(&Default::default(), window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  600        );
  601    });
  602}
  603
  604#[gpui::test]
  605fn test_clone(cx: &mut TestAppContext) {
  606    init_test(cx, |_| {});
  607
  608    let (text, selection_ranges) = marked_text_ranges(
  609        indoc! {"
  610            one
  611            two
  612            threeˇ
  613            four
  614            fiveˇ
  615        "},
  616        true,
  617    );
  618
  619    let editor = cx.add_window(|window, cx| {
  620        let buffer = MultiBuffer::build_simple(&text, cx);
  621        build_editor(buffer, window, cx)
  622    });
  623
  624    _ = editor.update(cx, |editor, window, cx| {
  625        editor.change_selections(None, window, cx, |s| {
  626            s.select_ranges(selection_ranges.clone())
  627        });
  628        editor.fold_creases(
  629            vec![
  630                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  631                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  632            ],
  633            true,
  634            window,
  635            cx,
  636        );
  637    });
  638
  639    let cloned_editor = editor
  640        .update(cx, |editor, _, cx| {
  641            cx.open_window(Default::default(), |window, cx| {
  642                cx.new(|cx| editor.clone(window, cx))
  643            })
  644        })
  645        .unwrap()
  646        .unwrap();
  647
  648    let snapshot = editor
  649        .update(cx, |e, window, cx| e.snapshot(window, cx))
  650        .unwrap();
  651    let cloned_snapshot = cloned_editor
  652        .update(cx, |e, window, cx| e.snapshot(window, cx))
  653        .unwrap();
  654
  655    assert_eq!(
  656        cloned_editor
  657            .update(cx, |e, _, cx| e.display_text(cx))
  658            .unwrap(),
  659        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  660    );
  661    assert_eq!(
  662        cloned_snapshot
  663            .folds_in_range(0..text.len())
  664            .collect::<Vec<_>>(),
  665        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  666    );
  667    assert_set_eq!(
  668        cloned_editor
  669            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  670            .unwrap(),
  671        editor
  672            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  673            .unwrap()
  674    );
  675    assert_set_eq!(
  676        cloned_editor
  677            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  678            .unwrap(),
  679        editor
  680            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  681            .unwrap()
  682    );
  683}
  684
  685#[gpui::test]
  686async fn test_navigation_history(cx: &mut TestAppContext) {
  687    init_test(cx, |_| {});
  688
  689    use workspace::item::Item;
  690
  691    let fs = FakeFs::new(cx.executor());
  692    let project = Project::test(fs, [], cx).await;
  693    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  694    let pane = workspace
  695        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  696        .unwrap();
  697
  698    _ = workspace.update(cx, |_v, window, cx| {
  699        cx.new(|cx| {
  700            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  701            let mut editor = build_editor(buffer.clone(), window, cx);
  702            let handle = cx.entity();
  703            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  704
  705            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  706                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  707            }
  708
  709            // Move the cursor a small distance.
  710            // Nothing is added to the navigation history.
  711            editor.change_selections(None, window, cx, |s| {
  712                s.select_display_ranges([
  713                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  714                ])
  715            });
  716            editor.change_selections(None, window, cx, |s| {
  717                s.select_display_ranges([
  718                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  719                ])
  720            });
  721            assert!(pop_history(&mut editor, cx).is_none());
  722
  723            // Move the cursor a large distance.
  724            // The history can jump back to the previous position.
  725            editor.change_selections(None, window, cx, |s| {
  726                s.select_display_ranges([
  727                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  728                ])
  729            });
  730            let nav_entry = pop_history(&mut editor, cx).unwrap();
  731            editor.navigate(nav_entry.data.unwrap(), window, cx);
  732            assert_eq!(nav_entry.item.id(), cx.entity_id());
  733            assert_eq!(
  734                editor.selections.display_ranges(cx),
  735                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  736            );
  737            assert!(pop_history(&mut editor, cx).is_none());
  738
  739            // Move the cursor a small distance via the mouse.
  740            // Nothing is added to the navigation history.
  741            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  742            editor.end_selection(window, cx);
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a large distance via the mouse.
  750            // The history can jump back to the previous position.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  756            );
  757            let nav_entry = pop_history(&mut editor, cx).unwrap();
  758            editor.navigate(nav_entry.data.unwrap(), window, cx);
  759            assert_eq!(nav_entry.item.id(), cx.entity_id());
  760            assert_eq!(
  761                editor.selections.display_ranges(cx),
  762                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  763            );
  764            assert!(pop_history(&mut editor, cx).is_none());
  765
  766            // Set scroll position to check later
  767            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  768            let original_scroll_position = editor.scroll_manager.anchor();
  769
  770            // Jump to the end of the document and adjust scroll
  771            editor.move_to_end(&MoveToEnd, window, cx);
  772            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  773            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  774
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  778
  779            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  780            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  781            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  782            let invalid_point = Point::new(9999, 0);
  783            editor.navigate(
  784                Box::new(NavigationData {
  785                    cursor_anchor: invalid_anchor,
  786                    cursor_position: invalid_point,
  787                    scroll_anchor: ScrollAnchor {
  788                        anchor: invalid_anchor,
  789                        offset: Default::default(),
  790                    },
  791                    scroll_top_row: invalid_point.row,
  792                }),
  793                window,
  794                cx,
  795            );
  796            assert_eq!(
  797                editor.selections.display_ranges(cx),
  798                &[editor.max_point(cx)..editor.max_point(cx)]
  799            );
  800            assert_eq!(
  801                editor.scroll_position(cx),
  802                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  803            );
  804
  805            editor
  806        })
  807    });
  808}
  809
  810#[gpui::test]
  811fn test_cancel(cx: &mut TestAppContext) {
  812    init_test(cx, |_| {});
  813
  814    let editor = cx.add_window(|window, cx| {
  815        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  816        build_editor(buffer, window, cx)
  817    });
  818
  819    _ = editor.update(cx, |editor, window, cx| {
  820        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  821        editor.update_selection(
  822            DisplayPoint::new(DisplayRow(1), 1),
  823            0,
  824            gpui::Point::<f32>::default(),
  825            window,
  826            cx,
  827        );
  828        editor.end_selection(window, cx);
  829
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(0), 3),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839        assert_eq!(
  840            editor.selections.display_ranges(cx),
  841            [
  842                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  843                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  844            ]
  845        );
  846    });
  847
  848    _ = editor.update(cx, |editor, window, cx| {
  849        editor.cancel(&Cancel, window, cx);
  850        assert_eq!(
  851            editor.selections.display_ranges(cx),
  852            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  853        );
  854    });
  855
  856    _ = editor.update(cx, |editor, window, cx| {
  857        editor.cancel(&Cancel, window, cx);
  858        assert_eq!(
  859            editor.selections.display_ranges(cx),
  860            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  861        );
  862    });
  863}
  864
  865#[gpui::test]
  866fn test_fold_action(cx: &mut TestAppContext) {
  867    init_test(cx, |_| {});
  868
  869    let editor = cx.add_window(|window, cx| {
  870        let buffer = MultiBuffer::build_simple(
  871            &"
  872                impl Foo {
  873                    // Hello!
  874
  875                    fn a() {
  876                        1
  877                    }
  878
  879                    fn b() {
  880                        2
  881                    }
  882
  883                    fn c() {
  884                        3
  885                    }
  886                }
  887            "
  888            .unindent(),
  889            cx,
  890        );
  891        build_editor(buffer.clone(), window, cx)
  892    });
  893
  894    _ = editor.update(cx, |editor, window, cx| {
  895        editor.change_selections(None, window, cx, |s| {
  896            s.select_display_ranges([
  897                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  898            ]);
  899        });
  900        editor.fold(&Fold, window, cx);
  901        assert_eq!(
  902            editor.display_text(cx),
  903            "
  904                impl Foo {
  905                    // Hello!
  906
  907                    fn a() {
  908                        1
  909                    }
  910
  911                    fn b() {⋯
  912                    }
  913
  914                    fn c() {⋯
  915                    }
  916                }
  917            "
  918            .unindent(),
  919        );
  920
  921        editor.fold(&Fold, window, cx);
  922        assert_eq!(
  923            editor.display_text(cx),
  924            "
  925                impl Foo {⋯
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.unfold_lines(&UnfoldLines, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {
  936                    // Hello!
  937
  938                    fn a() {
  939                        1
  940                    }
  941
  942                    fn b() {⋯
  943                    }
  944
  945                    fn c() {⋯
  946                    }
  947                }
  948            "
  949            .unindent(),
  950        );
  951
  952        editor.unfold_lines(&UnfoldLines, window, cx);
  953        assert_eq!(
  954            editor.display_text(cx),
  955            editor.buffer.read(cx).read(cx).text()
  956        );
  957    });
  958}
  959
  960#[gpui::test]
  961fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  962    init_test(cx, |_| {});
  963
  964    let editor = cx.add_window(|window, cx| {
  965        let buffer = MultiBuffer::build_simple(
  966            &"
  967                class Foo:
  968                    # Hello!
  969
  970                    def a():
  971                        print(1)
  972
  973                    def b():
  974                        print(2)
  975
  976                    def c():
  977                        print(3)
  978            "
  979            .unindent(),
  980            cx,
  981        );
  982        build_editor(buffer.clone(), window, cx)
  983    });
  984
  985    _ = editor.update(cx, |editor, window, cx| {
  986        editor.change_selections(None, window, cx, |s| {
  987            s.select_display_ranges([
  988                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  989            ]);
  990        });
  991        editor.fold(&Fold, window, cx);
  992        assert_eq!(
  993            editor.display_text(cx),
  994            "
  995                class Foo:
  996                    # Hello!
  997
  998                    def a():
  999                        print(1)
 1000
 1001                    def b():⋯
 1002
 1003                    def c():⋯
 1004            "
 1005            .unindent(),
 1006        );
 1007
 1008        editor.fold(&Fold, window, cx);
 1009        assert_eq!(
 1010            editor.display_text(cx),
 1011            "
 1012                class Foo:⋯
 1013            "
 1014            .unindent(),
 1015        );
 1016
 1017        editor.unfold_lines(&UnfoldLines, window, cx);
 1018        assert_eq!(
 1019            editor.display_text(cx),
 1020            "
 1021                class Foo:
 1022                    # Hello!
 1023
 1024                    def a():
 1025                        print(1)
 1026
 1027                    def b():⋯
 1028
 1029                    def c():⋯
 1030            "
 1031            .unindent(),
 1032        );
 1033
 1034        editor.unfold_lines(&UnfoldLines, window, cx);
 1035        assert_eq!(
 1036            editor.display_text(cx),
 1037            editor.buffer.read(cx).read(cx).text()
 1038        );
 1039    });
 1040}
 1041
 1042#[gpui::test]
 1043fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1044    init_test(cx, |_| {});
 1045
 1046    let editor = cx.add_window(|window, cx| {
 1047        let buffer = MultiBuffer::build_simple(
 1048            &"
 1049                class Foo:
 1050                    # Hello!
 1051
 1052                    def a():
 1053                        print(1)
 1054
 1055                    def b():
 1056                        print(2)
 1057
 1058
 1059                    def c():
 1060                        print(3)
 1061
 1062
 1063            "
 1064            .unindent(),
 1065            cx,
 1066        );
 1067        build_editor(buffer.clone(), window, cx)
 1068    });
 1069
 1070    _ = editor.update(cx, |editor, window, cx| {
 1071        editor.change_selections(None, window, cx, |s| {
 1072            s.select_display_ranges([
 1073                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1074            ]);
 1075        });
 1076        editor.fold(&Fold, window, cx);
 1077        assert_eq!(
 1078            editor.display_text(cx),
 1079            "
 1080                class Foo:
 1081                    # Hello!
 1082
 1083                    def a():
 1084                        print(1)
 1085
 1086                    def b():⋯
 1087
 1088
 1089                    def c():⋯
 1090
 1091
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                class Foo:⋯
 1101
 1102
 1103            "
 1104            .unindent(),
 1105        );
 1106
 1107        editor.unfold_lines(&UnfoldLines, window, cx);
 1108        assert_eq!(
 1109            editor.display_text(cx),
 1110            "
 1111                class Foo:
 1112                    # Hello!
 1113
 1114                    def a():
 1115                        print(1)
 1116
 1117                    def b():⋯
 1118
 1119
 1120                    def c():⋯
 1121
 1122
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_at_level(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151
 1152                class Bar:
 1153                    # World!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162            "
 1163            .unindent(),
 1164            cx,
 1165        );
 1166        build_editor(buffer.clone(), window, cx)
 1167    });
 1168
 1169    _ = editor.update(cx, |editor, window, cx| {
 1170        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1171        assert_eq!(
 1172            editor.display_text(cx),
 1173            "
 1174                class Foo:
 1175                    # Hello!
 1176
 1177                    def a():⋯
 1178
 1179                    def b():⋯
 1180
 1181
 1182                class Bar:
 1183                    # World!
 1184
 1185                    def a():⋯
 1186
 1187                    def b():⋯
 1188
 1189
 1190            "
 1191            .unindent(),
 1192        );
 1193
 1194        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1195        assert_eq!(
 1196            editor.display_text(cx),
 1197            "
 1198                class Foo:⋯
 1199
 1200
 1201                class Bar:⋯
 1202
 1203
 1204            "
 1205            .unindent(),
 1206        );
 1207
 1208        editor.unfold_all(&UnfoldAll, window, cx);
 1209        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            "
 1213                class Foo:
 1214                    # Hello!
 1215
 1216                    def a():
 1217                        print(1)
 1218
 1219                    def b():
 1220                        print(2)
 1221
 1222
 1223                class Bar:
 1224                    # World!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        assert_eq!(
 1238            editor.display_text(cx),
 1239            editor.buffer.read(cx).read(cx).text()
 1240        );
 1241    });
 1242}
 1243
 1244#[gpui::test]
 1245fn test_move_cursor(cx: &mut TestAppContext) {
 1246    init_test(cx, |_| {});
 1247
 1248    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1249    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1250
 1251    buffer.update(cx, |buffer, cx| {
 1252        buffer.edit(
 1253            vec![
 1254                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1255                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1256            ],
 1257            None,
 1258            cx,
 1259        );
 1260    });
 1261    _ = editor.update(cx, |editor, window, cx| {
 1262        assert_eq!(
 1263            editor.selections.display_ranges(cx),
 1264            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1265        );
 1266
 1267        editor.move_down(&MoveDown, window, cx);
 1268        assert_eq!(
 1269            editor.selections.display_ranges(cx),
 1270            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1271        );
 1272
 1273        editor.move_right(&MoveRight, window, cx);
 1274        assert_eq!(
 1275            editor.selections.display_ranges(cx),
 1276            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1277        );
 1278
 1279        editor.move_left(&MoveLeft, window, cx);
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1283        );
 1284
 1285        editor.move_up(&MoveUp, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1289        );
 1290
 1291        editor.move_to_end(&MoveToEnd, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1295        );
 1296
 1297        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1301        );
 1302
 1303        editor.change_selections(None, window, cx, |s| {
 1304            s.select_display_ranges([
 1305                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1306            ]);
 1307        });
 1308        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1309        assert_eq!(
 1310            editor.selections.display_ranges(cx),
 1311            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1312        );
 1313
 1314        editor.select_to_end(&SelectToEnd, window, cx);
 1315        assert_eq!(
 1316            editor.selections.display_ranges(cx),
 1317            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1318        );
 1319    });
 1320}
 1321
 1322#[gpui::test]
 1323fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1324    init_test(cx, |_| {});
 1325
 1326    let editor = cx.add_window(|window, cx| {
 1327        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1328        build_editor(buffer.clone(), window, cx)
 1329    });
 1330
 1331    assert_eq!('🟥'.len_utf8(), 4);
 1332    assert_eq!('α'.len_utf8(), 2);
 1333
 1334    _ = editor.update(cx, |editor, window, cx| {
 1335        editor.fold_creases(
 1336            vec![
 1337                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1338                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1339                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1340            ],
 1341            true,
 1342            window,
 1343            cx,
 1344        );
 1345        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1346
 1347        editor.move_right(&MoveRight, window, cx);
 1348        assert_eq!(
 1349            editor.selections.display_ranges(cx),
 1350            &[empty_range(0, "🟥".len())]
 1351        );
 1352        editor.move_right(&MoveRight, window, cx);
 1353        assert_eq!(
 1354            editor.selections.display_ranges(cx),
 1355            &[empty_range(0, "🟥🟧".len())]
 1356        );
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥🟧⋯".len())]
 1361        );
 1362
 1363        editor.move_down(&MoveDown, window, cx);
 1364        assert_eq!(
 1365            editor.selections.display_ranges(cx),
 1366            &[empty_range(1, "ab⋯e".len())]
 1367        );
 1368        editor.move_left(&MoveLeft, window, cx);
 1369        assert_eq!(
 1370            editor.selections.display_ranges(cx),
 1371            &[empty_range(1, "ab⋯".len())]
 1372        );
 1373        editor.move_left(&MoveLeft, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "a".len())]
 1382        );
 1383
 1384        editor.move_down(&MoveDown, window, cx);
 1385        assert_eq!(
 1386            editor.selections.display_ranges(cx),
 1387            &[empty_range(2, "α".len())]
 1388        );
 1389        editor.move_right(&MoveRight, window, cx);
 1390        assert_eq!(
 1391            editor.selections.display_ranges(cx),
 1392            &[empty_range(2, "αβ".len())]
 1393        );
 1394        editor.move_right(&MoveRight, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "αβ⋯".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ⋯ε".len())]
 1403        );
 1404
 1405        editor.move_up(&MoveUp, window, cx);
 1406        assert_eq!(
 1407            editor.selections.display_ranges(cx),
 1408            &[empty_range(1, "ab⋯e".len())]
 1409        );
 1410        editor.move_down(&MoveDown, window, cx);
 1411        assert_eq!(
 1412            editor.selections.display_ranges(cx),
 1413            &[empty_range(2, "αβ⋯ε".len())]
 1414        );
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420
 1421        editor.move_up(&MoveUp, window, cx);
 1422        assert_eq!(
 1423            editor.selections.display_ranges(cx),
 1424            &[empty_range(0, "🟥🟧".len())]
 1425        );
 1426        editor.move_left(&MoveLeft, window, cx);
 1427        assert_eq!(
 1428            editor.selections.display_ranges(cx),
 1429            &[empty_range(0, "🟥".len())]
 1430        );
 1431        editor.move_left(&MoveLeft, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "".len())]
 1435        );
 1436    });
 1437}
 1438
 1439#[gpui::test]
 1440fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1441    init_test(cx, |_| {});
 1442
 1443    let editor = cx.add_window(|window, cx| {
 1444        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1445        build_editor(buffer.clone(), window, cx)
 1446    });
 1447    _ = editor.update(cx, |editor, window, cx| {
 1448        editor.change_selections(None, window, cx, |s| {
 1449            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1450        });
 1451
 1452        // moving above start of document should move selection to start of document,
 1453        // but the next move down should still be at the original goal_x
 1454        editor.move_up(&MoveUp, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[empty_range(0, "".len())]
 1458        );
 1459
 1460        editor.move_down(&MoveDown, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[empty_range(1, "abcd".len())]
 1464        );
 1465
 1466        editor.move_down(&MoveDown, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[empty_range(2, "αβγ".len())]
 1470        );
 1471
 1472        editor.move_down(&MoveDown, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(3, "abcd".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1482        );
 1483
 1484        // moving past end of document should not change goal_x
 1485        editor.move_down(&MoveDown, window, cx);
 1486        assert_eq!(
 1487            editor.selections.display_ranges(cx),
 1488            &[empty_range(5, "".len())]
 1489        );
 1490
 1491        editor.move_down(&MoveDown, window, cx);
 1492        assert_eq!(
 1493            editor.selections.display_ranges(cx),
 1494            &[empty_range(5, "".len())]
 1495        );
 1496
 1497        editor.move_up(&MoveUp, window, cx);
 1498        assert_eq!(
 1499            editor.selections.display_ranges(cx),
 1500            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1501        );
 1502
 1503        editor.move_up(&MoveUp, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(3, "abcd".len())]
 1507        );
 1508
 1509        editor.move_up(&MoveUp, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(2, "αβγ".len())]
 1513        );
 1514    });
 1515}
 1516
 1517#[gpui::test]
 1518fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1519    init_test(cx, |_| {});
 1520    let move_to_beg = MoveToBeginningOfLine {
 1521        stop_at_soft_wraps: true,
 1522        stop_at_indent: true,
 1523    };
 1524
 1525    let delete_to_beg = DeleteToBeginningOfLine {
 1526        stop_at_indent: false,
 1527    };
 1528
 1529    let move_to_end = MoveToEndOfLine {
 1530        stop_at_soft_wraps: true,
 1531    };
 1532
 1533    let editor = cx.add_window(|window, cx| {
 1534        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1535        build_editor(buffer, window, cx)
 1536    });
 1537    _ = editor.update(cx, |editor, window, cx| {
 1538        editor.change_selections(None, window, cx, |s| {
 1539            s.select_display_ranges([
 1540                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1541                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1542            ]);
 1543        });
 1544    });
 1545
 1546    _ = editor.update(cx, |editor, window, cx| {
 1547        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1548        assert_eq!(
 1549            editor.selections.display_ranges(cx),
 1550            &[
 1551                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1552                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1553            ]
 1554        );
 1555    });
 1556
 1557    _ = editor.update(cx, |editor, window, cx| {
 1558        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1559        assert_eq!(
 1560            editor.selections.display_ranges(cx),
 1561            &[
 1562                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1563                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1564            ]
 1565        );
 1566    });
 1567
 1568    _ = editor.update(cx, |editor, window, cx| {
 1569        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1570        assert_eq!(
 1571            editor.selections.display_ranges(cx),
 1572            &[
 1573                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1574                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1575            ]
 1576        );
 1577    });
 1578
 1579    _ = editor.update(cx, |editor, window, cx| {
 1580        editor.move_to_end_of_line(&move_to_end, window, cx);
 1581        assert_eq!(
 1582            editor.selections.display_ranges(cx),
 1583            &[
 1584                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1585                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1586            ]
 1587        );
 1588    });
 1589
 1590    // Moving to the end of line again is a no-op.
 1591    _ = editor.update(cx, |editor, window, cx| {
 1592        editor.move_to_end_of_line(&move_to_end, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[
 1596                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1597                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1598            ]
 1599        );
 1600    });
 1601
 1602    _ = editor.update(cx, |editor, window, cx| {
 1603        editor.move_left(&MoveLeft, window, cx);
 1604        editor.select_to_beginning_of_line(
 1605            &SelectToBeginningOfLine {
 1606                stop_at_soft_wraps: true,
 1607                stop_at_indent: true,
 1608            },
 1609            window,
 1610            cx,
 1611        );
 1612        assert_eq!(
 1613            editor.selections.display_ranges(cx),
 1614            &[
 1615                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1616                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1617            ]
 1618        );
 1619    });
 1620
 1621    _ = editor.update(cx, |editor, window, cx| {
 1622        editor.select_to_beginning_of_line(
 1623            &SelectToBeginningOfLine {
 1624                stop_at_soft_wraps: true,
 1625                stop_at_indent: true,
 1626            },
 1627            window,
 1628            cx,
 1629        );
 1630        assert_eq!(
 1631            editor.selections.display_ranges(cx),
 1632            &[
 1633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1634                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1635            ]
 1636        );
 1637    });
 1638
 1639    _ = editor.update(cx, |editor, window, cx| {
 1640        editor.select_to_beginning_of_line(
 1641            &SelectToBeginningOfLine {
 1642                stop_at_soft_wraps: true,
 1643                stop_at_indent: true,
 1644            },
 1645            window,
 1646            cx,
 1647        );
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[
 1651                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1652                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1653            ]
 1654        );
 1655    });
 1656
 1657    _ = editor.update(cx, |editor, window, cx| {
 1658        editor.select_to_end_of_line(
 1659            &SelectToEndOfLine {
 1660                stop_at_soft_wraps: true,
 1661            },
 1662            window,
 1663            cx,
 1664        );
 1665        assert_eq!(
 1666            editor.selections.display_ranges(cx),
 1667            &[
 1668                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1669                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1670            ]
 1671        );
 1672    });
 1673
 1674    _ = editor.update(cx, |editor, window, cx| {
 1675        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1676        assert_eq!(editor.display_text(cx), "ab\n  de");
 1677        assert_eq!(
 1678            editor.selections.display_ranges(cx),
 1679            &[
 1680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1681                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1682            ]
 1683        );
 1684    });
 1685
 1686    _ = editor.update(cx, |editor, window, cx| {
 1687        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1688        assert_eq!(editor.display_text(cx), "\n");
 1689        assert_eq!(
 1690            editor.selections.display_ranges(cx),
 1691            &[
 1692                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1693                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1694            ]
 1695        );
 1696    });
 1697}
 1698
 1699#[gpui::test]
 1700fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1701    init_test(cx, |_| {});
 1702    let move_to_beg = MoveToBeginningOfLine {
 1703        stop_at_soft_wraps: false,
 1704        stop_at_indent: false,
 1705    };
 1706
 1707    let move_to_end = MoveToEndOfLine {
 1708        stop_at_soft_wraps: false,
 1709    };
 1710
 1711    let editor = cx.add_window(|window, cx| {
 1712        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1713        build_editor(buffer, window, cx)
 1714    });
 1715
 1716    _ = editor.update(cx, |editor, window, cx| {
 1717        editor.set_wrap_width(Some(140.0.into()), cx);
 1718
 1719        // We expect the following lines after wrapping
 1720        // ```
 1721        // thequickbrownfox
 1722        // jumpedoverthelazydo
 1723        // gs
 1724        // ```
 1725        // The final `gs` was soft-wrapped onto a new line.
 1726        assert_eq!(
 1727            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1728            editor.display_text(cx),
 1729        );
 1730
 1731        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1732        // Start the cursor at the `k` on the first line
 1733        editor.change_selections(None, window, cx, |s| {
 1734            s.select_display_ranges([
 1735                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1736            ]);
 1737        });
 1738
 1739        // Moving to the beginning of the line should put us at the beginning of the line.
 1740        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1741        assert_eq!(
 1742            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1743            editor.selections.display_ranges(cx)
 1744        );
 1745
 1746        // Moving to the end of the line should put us at the end of the line.
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1750            editor.selections.display_ranges(cx)
 1751        );
 1752
 1753        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1754        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1755        editor.change_selections(None, window, cx, |s| {
 1756            s.select_display_ranges([
 1757                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1758            ]);
 1759        });
 1760
 1761        // Moving to the beginning of the line should put us at the start of the second line of
 1762        // display text, i.e., the `j`.
 1763        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1764        assert_eq!(
 1765            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1766            editor.selections.display_ranges(cx)
 1767        );
 1768
 1769        // Moving to the beginning of the line again should be a no-op.
 1770        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1771        assert_eq!(
 1772            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1773            editor.selections.display_ranges(cx)
 1774        );
 1775
 1776        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1777        // next display line.
 1778        editor.move_to_end_of_line(&move_to_end, window, cx);
 1779        assert_eq!(
 1780            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1781            editor.selections.display_ranges(cx)
 1782        );
 1783
 1784        // Moving to the end of the line again should be a no-op.
 1785        editor.move_to_end_of_line(&move_to_end, window, cx);
 1786        assert_eq!(
 1787            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1788            editor.selections.display_ranges(cx)
 1789        );
 1790    });
 1791}
 1792
 1793#[gpui::test]
 1794fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1795    init_test(cx, |_| {});
 1796
 1797    let move_to_beg = MoveToBeginningOfLine {
 1798        stop_at_soft_wraps: true,
 1799        stop_at_indent: true,
 1800    };
 1801
 1802    let select_to_beg = SelectToBeginningOfLine {
 1803        stop_at_soft_wraps: true,
 1804        stop_at_indent: true,
 1805    };
 1806
 1807    let delete_to_beg = DeleteToBeginningOfLine {
 1808        stop_at_indent: true,
 1809    };
 1810
 1811    let move_to_end = MoveToEndOfLine {
 1812        stop_at_soft_wraps: false,
 1813    };
 1814
 1815    let editor = cx.add_window(|window, cx| {
 1816        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1817        build_editor(buffer, window, cx)
 1818    });
 1819
 1820    _ = editor.update(cx, |editor, window, cx| {
 1821        editor.change_selections(None, window, cx, |s| {
 1822            s.select_display_ranges([
 1823                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1825            ]);
 1826        });
 1827
 1828        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1829        // and the second cursor at the first non-whitespace character in the line.
 1830        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1831        assert_eq!(
 1832            editor.selections.display_ranges(cx),
 1833            &[
 1834                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1835                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1836            ]
 1837        );
 1838
 1839        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1840        // and should move the second cursor to the beginning of the line.
 1841        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1842        assert_eq!(
 1843            editor.selections.display_ranges(cx),
 1844            &[
 1845                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1846                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1847            ]
 1848        );
 1849
 1850        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1851        // and should move the second cursor back to the first non-whitespace character in the line.
 1852        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1853        assert_eq!(
 1854            editor.selections.display_ranges(cx),
 1855            &[
 1856                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1857                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1858            ]
 1859        );
 1860
 1861        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1862        // and to the first non-whitespace character in the line for the second cursor.
 1863        editor.move_to_end_of_line(&move_to_end, window, cx);
 1864        editor.move_left(&MoveLeft, window, cx);
 1865        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1866        assert_eq!(
 1867            editor.selections.display_ranges(cx),
 1868            &[
 1869                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1870                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1871            ]
 1872        );
 1873
 1874        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1875        // and should select to the beginning of the line for the second cursor.
 1876        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1877        assert_eq!(
 1878            editor.selections.display_ranges(cx),
 1879            &[
 1880                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1881                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1882            ]
 1883        );
 1884
 1885        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1886        // and should delete to the first non-whitespace character in the line for the second cursor.
 1887        editor.move_to_end_of_line(&move_to_end, window, cx);
 1888        editor.move_left(&MoveLeft, window, cx);
 1889        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1890        assert_eq!(editor.text(cx), "c\n  f");
 1891    });
 1892}
 1893
 1894#[gpui::test]
 1895fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1896    init_test(cx, |_| {});
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902    _ = editor.update(cx, |editor, window, cx| {
 1903        editor.change_selections(None, window, cx, |s| {
 1904            s.select_display_ranges([
 1905                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1906                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1907            ])
 1908        });
 1909
 1910        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1911        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1912
 1913        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1914        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1915
 1916        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1917        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1918
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1926        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1929        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1932        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1933
 1934        editor.move_right(&MoveRight, window, cx);
 1935        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1936        assert_selection_ranges(
 1937            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1938            editor,
 1939            cx,
 1940        );
 1941
 1942        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1943        assert_selection_ranges(
 1944            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1945            editor,
 1946            cx,
 1947        );
 1948
 1949        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1950        assert_selection_ranges(
 1951            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1952            editor,
 1953            cx,
 1954        );
 1955    });
 1956}
 1957
 1958#[gpui::test]
 1959fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1960    init_test(cx, |_| {});
 1961
 1962    let editor = cx.add_window(|window, cx| {
 1963        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1964        build_editor(buffer, window, cx)
 1965    });
 1966
 1967    _ = editor.update(cx, |editor, window, cx| {
 1968        editor.set_wrap_width(Some(140.0.into()), cx);
 1969        assert_eq!(
 1970            editor.display_text(cx),
 1971            "use one::{\n    two::three::\n    four::five\n};"
 1972        );
 1973
 1974        editor.change_selections(None, window, cx, |s| {
 1975            s.select_display_ranges([
 1976                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1977            ]);
 1978        });
 1979
 1980        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1981        assert_eq!(
 1982            editor.selections.display_ranges(cx),
 1983            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1984        );
 1985
 1986        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1987        assert_eq!(
 1988            editor.selections.display_ranges(cx),
 1989            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1990        );
 1991
 1992        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1996        );
 1997
 1998        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1999        assert_eq!(
 2000            editor.selections.display_ranges(cx),
 2001            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2002        );
 2003
 2004        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2005        assert_eq!(
 2006            editor.selections.display_ranges(cx),
 2007            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2008        );
 2009
 2010        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2011        assert_eq!(
 2012            editor.selections.display_ranges(cx),
 2013            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2014        );
 2015    });
 2016}
 2017
 2018#[gpui::test]
 2019async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2020    init_test(cx, |_| {});
 2021    let mut cx = EditorTestContext::new(cx).await;
 2022
 2023    let line_height = cx.editor(|editor, window, _| {
 2024        editor
 2025            .style()
 2026            .unwrap()
 2027            .text
 2028            .line_height_in_pixels(window.rem_size())
 2029    });
 2030    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2031
 2032    cx.set_state(
 2033        &r#"ˇone
 2034        two
 2035
 2036        three
 2037        fourˇ
 2038        five
 2039
 2040        six"#
 2041            .unindent(),
 2042    );
 2043
 2044    cx.update_editor(|editor, window, cx| {
 2045        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2046    });
 2047    cx.assert_editor_state(
 2048        &r#"one
 2049        two
 2050        ˇ
 2051        three
 2052        four
 2053        five
 2054        ˇ
 2055        six"#
 2056            .unindent(),
 2057    );
 2058
 2059    cx.update_editor(|editor, window, cx| {
 2060        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2061    });
 2062    cx.assert_editor_state(
 2063        &r#"one
 2064        two
 2065
 2066        three
 2067        four
 2068        five
 2069        ˇ
 2070        sixˇ"#
 2071            .unindent(),
 2072    );
 2073
 2074    cx.update_editor(|editor, window, cx| {
 2075        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2076    });
 2077    cx.assert_editor_state(
 2078        &r#"one
 2079        two
 2080
 2081        three
 2082        four
 2083        five
 2084
 2085        sixˇ"#
 2086            .unindent(),
 2087    );
 2088
 2089    cx.update_editor(|editor, window, cx| {
 2090        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2091    });
 2092    cx.assert_editor_state(
 2093        &r#"one
 2094        two
 2095
 2096        three
 2097        four
 2098        five
 2099        ˇ
 2100        six"#
 2101            .unindent(),
 2102    );
 2103
 2104    cx.update_editor(|editor, window, cx| {
 2105        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2106    });
 2107    cx.assert_editor_state(
 2108        &r#"one
 2109        two
 2110        ˇ
 2111        three
 2112        four
 2113        five
 2114
 2115        six"#
 2116            .unindent(),
 2117    );
 2118
 2119    cx.update_editor(|editor, window, cx| {
 2120        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2121    });
 2122    cx.assert_editor_state(
 2123        &r#"ˇone
 2124        two
 2125
 2126        three
 2127        four
 2128        five
 2129
 2130        six"#
 2131            .unindent(),
 2132    );
 2133}
 2134
 2135#[gpui::test]
 2136async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2137    init_test(cx, |_| {});
 2138    let mut cx = EditorTestContext::new(cx).await;
 2139    let line_height = cx.editor(|editor, window, _| {
 2140        editor
 2141            .style()
 2142            .unwrap()
 2143            .text
 2144            .line_height_in_pixels(window.rem_size())
 2145    });
 2146    let window = cx.window;
 2147    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2148
 2149    cx.set_state(
 2150        r#"ˇone
 2151        two
 2152        three
 2153        four
 2154        five
 2155        six
 2156        seven
 2157        eight
 2158        nine
 2159        ten
 2160        "#,
 2161    );
 2162
 2163    cx.update_editor(|editor, window, cx| {
 2164        assert_eq!(
 2165            editor.snapshot(window, cx).scroll_position(),
 2166            gpui::Point::new(0., 0.)
 2167        );
 2168        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2169        assert_eq!(
 2170            editor.snapshot(window, cx).scroll_position(),
 2171            gpui::Point::new(0., 3.)
 2172        );
 2173        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2174        assert_eq!(
 2175            editor.snapshot(window, cx).scroll_position(),
 2176            gpui::Point::new(0., 6.)
 2177        );
 2178        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2179        assert_eq!(
 2180            editor.snapshot(window, cx).scroll_position(),
 2181            gpui::Point::new(0., 3.)
 2182        );
 2183
 2184        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2185        assert_eq!(
 2186            editor.snapshot(window, cx).scroll_position(),
 2187            gpui::Point::new(0., 1.)
 2188        );
 2189        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2190        assert_eq!(
 2191            editor.snapshot(window, cx).scroll_position(),
 2192            gpui::Point::new(0., 3.)
 2193        );
 2194    });
 2195}
 2196
 2197#[gpui::test]
 2198async fn test_autoscroll(cx: &mut TestAppContext) {
 2199    init_test(cx, |_| {});
 2200    let mut cx = EditorTestContext::new(cx).await;
 2201
 2202    let line_height = cx.update_editor(|editor, window, cx| {
 2203        editor.set_vertical_scroll_margin(2, cx);
 2204        editor
 2205            .style()
 2206            .unwrap()
 2207            .text
 2208            .line_height_in_pixels(window.rem_size())
 2209    });
 2210    let window = cx.window;
 2211    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2212
 2213    cx.set_state(
 2214        r#"ˇone
 2215            two
 2216            three
 2217            four
 2218            five
 2219            six
 2220            seven
 2221            eight
 2222            nine
 2223            ten
 2224        "#,
 2225    );
 2226    cx.update_editor(|editor, window, cx| {
 2227        assert_eq!(
 2228            editor.snapshot(window, cx).scroll_position(),
 2229            gpui::Point::new(0., 0.0)
 2230        );
 2231    });
 2232
 2233    // Add a cursor below the visible area. Since both cursors cannot fit
 2234    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2235    // allows the vertical scroll margin below that cursor.
 2236    cx.update_editor(|editor, window, cx| {
 2237        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2238            selections.select_ranges([
 2239                Point::new(0, 0)..Point::new(0, 0),
 2240                Point::new(6, 0)..Point::new(6, 0),
 2241            ]);
 2242        })
 2243    });
 2244    cx.update_editor(|editor, window, cx| {
 2245        assert_eq!(
 2246            editor.snapshot(window, cx).scroll_position(),
 2247            gpui::Point::new(0., 3.0)
 2248        );
 2249    });
 2250
 2251    // Move down. The editor cursor scrolls down to track the newest cursor.
 2252    cx.update_editor(|editor, window, cx| {
 2253        editor.move_down(&Default::default(), window, cx);
 2254    });
 2255    cx.update_editor(|editor, window, cx| {
 2256        assert_eq!(
 2257            editor.snapshot(window, cx).scroll_position(),
 2258            gpui::Point::new(0., 4.0)
 2259        );
 2260    });
 2261
 2262    // Add a cursor above the visible area. Since both cursors fit on screen,
 2263    // the editor scrolls to show both.
 2264    cx.update_editor(|editor, window, cx| {
 2265        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2266            selections.select_ranges([
 2267                Point::new(1, 0)..Point::new(1, 0),
 2268                Point::new(6, 0)..Point::new(6, 0),
 2269            ]);
 2270        })
 2271    });
 2272    cx.update_editor(|editor, window, cx| {
 2273        assert_eq!(
 2274            editor.snapshot(window, cx).scroll_position(),
 2275            gpui::Point::new(0., 1.0)
 2276        );
 2277    });
 2278}
 2279
 2280#[gpui::test]
 2281async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2282    init_test(cx, |_| {});
 2283    let mut cx = EditorTestContext::new(cx).await;
 2284
 2285    let line_height = cx.editor(|editor, window, _cx| {
 2286        editor
 2287            .style()
 2288            .unwrap()
 2289            .text
 2290            .line_height_in_pixels(window.rem_size())
 2291    });
 2292    let window = cx.window;
 2293    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2294    cx.set_state(
 2295        &r#"
 2296        ˇone
 2297        two
 2298        threeˇ
 2299        four
 2300        five
 2301        six
 2302        seven
 2303        eight
 2304        nine
 2305        ten
 2306        "#
 2307        .unindent(),
 2308    );
 2309
 2310    cx.update_editor(|editor, window, cx| {
 2311        editor.move_page_down(&MovePageDown::default(), window, cx)
 2312    });
 2313    cx.assert_editor_state(
 2314        &r#"
 2315        one
 2316        two
 2317        three
 2318        ˇfour
 2319        five
 2320        sixˇ
 2321        seven
 2322        eight
 2323        nine
 2324        ten
 2325        "#
 2326        .unindent(),
 2327    );
 2328
 2329    cx.update_editor(|editor, window, cx| {
 2330        editor.move_page_down(&MovePageDown::default(), window, cx)
 2331    });
 2332    cx.assert_editor_state(
 2333        &r#"
 2334        one
 2335        two
 2336        three
 2337        four
 2338        five
 2339        six
 2340        ˇseven
 2341        eight
 2342        nineˇ
 2343        ten
 2344        "#
 2345        .unindent(),
 2346    );
 2347
 2348    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2349    cx.assert_editor_state(
 2350        &r#"
 2351        one
 2352        two
 2353        three
 2354        ˇfour
 2355        five
 2356        sixˇ
 2357        seven
 2358        eight
 2359        nine
 2360        ten
 2361        "#
 2362        .unindent(),
 2363    );
 2364
 2365    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2366    cx.assert_editor_state(
 2367        &r#"
 2368        ˇone
 2369        two
 2370        threeˇ
 2371        four
 2372        five
 2373        six
 2374        seven
 2375        eight
 2376        nine
 2377        ten
 2378        "#
 2379        .unindent(),
 2380    );
 2381
 2382    // Test select collapsing
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386        editor.move_page_down(&MovePageDown::default(), window, cx);
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        seven
 2397        eight
 2398        nine
 2399        ˇten
 2400        ˇ"#
 2401        .unindent(),
 2402    );
 2403}
 2404
 2405#[gpui::test]
 2406async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2407    init_test(cx, |_| {});
 2408    let mut cx = EditorTestContext::new(cx).await;
 2409    cx.set_state("one «two threeˇ» four");
 2410    cx.update_editor(|editor, window, cx| {
 2411        editor.delete_to_beginning_of_line(
 2412            &DeleteToBeginningOfLine {
 2413                stop_at_indent: false,
 2414            },
 2415            window,
 2416            cx,
 2417        );
 2418        assert_eq!(editor.text(cx), " four");
 2419    });
 2420}
 2421
 2422#[gpui::test]
 2423fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2424    init_test(cx, |_| {});
 2425
 2426    let editor = cx.add_window(|window, cx| {
 2427        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2428        build_editor(buffer.clone(), window, cx)
 2429    });
 2430
 2431    _ = editor.update(cx, |editor, window, cx| {
 2432        editor.change_selections(None, window, cx, |s| {
 2433            s.select_display_ranges([
 2434                // an empty selection - the preceding word fragment is deleted
 2435                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2436                // characters selected - they are deleted
 2437                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2438            ])
 2439        });
 2440        editor.delete_to_previous_word_start(
 2441            &DeleteToPreviousWordStart {
 2442                ignore_newlines: false,
 2443            },
 2444            window,
 2445            cx,
 2446        );
 2447        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2448    });
 2449
 2450    _ = editor.update(cx, |editor, window, cx| {
 2451        editor.change_selections(None, window, cx, |s| {
 2452            s.select_display_ranges([
 2453                // an empty selection - the following word fragment is deleted
 2454                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2455                // characters selected - they are deleted
 2456                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2457            ])
 2458        });
 2459        editor.delete_to_next_word_end(
 2460            &DeleteToNextWordEnd {
 2461                ignore_newlines: false,
 2462            },
 2463            window,
 2464            cx,
 2465        );
 2466        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2467    });
 2468}
 2469
 2470#[gpui::test]
 2471fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2472    init_test(cx, |_| {});
 2473
 2474    let editor = cx.add_window(|window, cx| {
 2475        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2476        build_editor(buffer.clone(), window, cx)
 2477    });
 2478    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2479        ignore_newlines: false,
 2480    };
 2481    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2482        ignore_newlines: true,
 2483    };
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(None, window, cx, |s| {
 2487            s.select_display_ranges([
 2488                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2489            ])
 2490        });
 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\n");
 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\nthree");
 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\n");
 2497        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2498        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 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(), "one\n");
 2501        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2502        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2503    });
 2504}
 2505
 2506#[gpui::test]
 2507fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2508    init_test(cx, |_| {});
 2509
 2510    let editor = cx.add_window(|window, cx| {
 2511        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2512        build_editor(buffer.clone(), window, cx)
 2513    });
 2514    let del_to_next_word_end = DeleteToNextWordEnd {
 2515        ignore_newlines: false,
 2516    };
 2517    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2518        ignore_newlines: true,
 2519    };
 2520
 2521    _ = editor.update(cx, |editor, window, cx| {
 2522        editor.change_selections(None, window, cx, |s| {
 2523            s.select_display_ranges([
 2524                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2525            ])
 2526        });
 2527        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2528        assert_eq!(
 2529            editor.buffer.read(cx).read(cx).text(),
 2530            "one\n   two\nthree\n   four"
 2531        );
 2532        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2533        assert_eq!(
 2534            editor.buffer.read(cx).read(cx).text(),
 2535            "\n   two\nthree\n   four"
 2536        );
 2537        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2538        assert_eq!(
 2539            editor.buffer.read(cx).read(cx).text(),
 2540            "two\nthree\n   four"
 2541        );
 2542        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2543        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\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(), "\n   four");
 2546        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2547        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2548    });
 2549}
 2550
 2551#[gpui::test]
 2552fn test_newline(cx: &mut TestAppContext) {
 2553    init_test(cx, |_| {});
 2554
 2555    let editor = cx.add_window(|window, cx| {
 2556        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2557        build_editor(buffer.clone(), window, cx)
 2558    });
 2559
 2560    _ = editor.update(cx, |editor, window, cx| {
 2561        editor.change_selections(None, window, cx, |s| {
 2562            s.select_display_ranges([
 2563                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2565                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2566            ])
 2567        });
 2568
 2569        editor.newline(&Newline, window, cx);
 2570        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2571    });
 2572}
 2573
 2574#[gpui::test]
 2575fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2576    init_test(cx, |_| {});
 2577
 2578    let editor = cx.add_window(|window, cx| {
 2579        let buffer = MultiBuffer::build_simple(
 2580            "
 2581                a
 2582                b(
 2583                    X
 2584                )
 2585                c(
 2586                    X
 2587                )
 2588            "
 2589            .unindent()
 2590            .as_str(),
 2591            cx,
 2592        );
 2593        let mut editor = build_editor(buffer.clone(), window, cx);
 2594        editor.change_selections(None, window, cx, |s| {
 2595            s.select_ranges([
 2596                Point::new(2, 4)..Point::new(2, 5),
 2597                Point::new(5, 4)..Point::new(5, 5),
 2598            ])
 2599        });
 2600        editor
 2601    });
 2602
 2603    _ = editor.update(cx, |editor, window, cx| {
 2604        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2605        editor.buffer.update(cx, |buffer, cx| {
 2606            buffer.edit(
 2607                [
 2608                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2609                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2610                ],
 2611                None,
 2612                cx,
 2613            );
 2614            assert_eq!(
 2615                buffer.read(cx).text(),
 2616                "
 2617                    a
 2618                    b()
 2619                    c()
 2620                "
 2621                .unindent()
 2622            );
 2623        });
 2624        assert_eq!(
 2625            editor.selections.ranges(cx),
 2626            &[
 2627                Point::new(1, 2)..Point::new(1, 2),
 2628                Point::new(2, 2)..Point::new(2, 2),
 2629            ],
 2630        );
 2631
 2632        editor.newline(&Newline, window, cx);
 2633        assert_eq!(
 2634            editor.text(cx),
 2635            "
 2636                a
 2637                b(
 2638                )
 2639                c(
 2640                )
 2641            "
 2642            .unindent()
 2643        );
 2644
 2645        // The selections are moved after the inserted newlines
 2646        assert_eq!(
 2647            editor.selections.ranges(cx),
 2648            &[
 2649                Point::new(2, 0)..Point::new(2, 0),
 2650                Point::new(4, 0)..Point::new(4, 0),
 2651            ],
 2652        );
 2653    });
 2654}
 2655
 2656#[gpui::test]
 2657async fn test_newline_above(cx: &mut TestAppContext) {
 2658    init_test(cx, |settings| {
 2659        settings.defaults.tab_size = NonZeroU32::new(4)
 2660    });
 2661
 2662    let language = Arc::new(
 2663        Language::new(
 2664            LanguageConfig::default(),
 2665            Some(tree_sitter_rust::LANGUAGE.into()),
 2666        )
 2667        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2668        .unwrap(),
 2669    );
 2670
 2671    let mut cx = EditorTestContext::new(cx).await;
 2672    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2673    cx.set_state(indoc! {"
 2674        const a: ˇA = (
 2675 2676                «const_functionˇ»(ˇ),
 2677                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2678 2679        ˇ);ˇ
 2680    "});
 2681
 2682    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2683    cx.assert_editor_state(indoc! {"
 2684        ˇ
 2685        const a: A = (
 2686            ˇ
 2687            (
 2688                ˇ
 2689                ˇ
 2690                const_function(),
 2691                ˇ
 2692                ˇ
 2693                ˇ
 2694                ˇ
 2695                something_else,
 2696                ˇ
 2697            )
 2698            ˇ
 2699            ˇ
 2700        );
 2701    "});
 2702}
 2703
 2704#[gpui::test]
 2705async fn test_newline_below(cx: &mut TestAppContext) {
 2706    init_test(cx, |settings| {
 2707        settings.defaults.tab_size = NonZeroU32::new(4)
 2708    });
 2709
 2710    let language = Arc::new(
 2711        Language::new(
 2712            LanguageConfig::default(),
 2713            Some(tree_sitter_rust::LANGUAGE.into()),
 2714        )
 2715        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2716        .unwrap(),
 2717    );
 2718
 2719    let mut cx = EditorTestContext::new(cx).await;
 2720    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2721    cx.set_state(indoc! {"
 2722        const a: ˇA = (
 2723 2724                «const_functionˇ»(ˇ),
 2725                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2726 2727        ˇ);ˇ
 2728    "});
 2729
 2730    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2731    cx.assert_editor_state(indoc! {"
 2732        const a: A = (
 2733            ˇ
 2734            (
 2735                ˇ
 2736                const_function(),
 2737                ˇ
 2738                ˇ
 2739                something_else,
 2740                ˇ
 2741                ˇ
 2742                ˇ
 2743                ˇ
 2744            )
 2745            ˇ
 2746        );
 2747        ˇ
 2748        ˇ
 2749    "});
 2750}
 2751
 2752#[gpui::test]
 2753async fn test_newline_comments(cx: &mut TestAppContext) {
 2754    init_test(cx, |settings| {
 2755        settings.defaults.tab_size = NonZeroU32::new(4)
 2756    });
 2757
 2758    let language = Arc::new(Language::new(
 2759        LanguageConfig {
 2760            line_comments: vec!["// ".into()],
 2761            ..LanguageConfig::default()
 2762        },
 2763        None,
 2764    ));
 2765    {
 2766        let mut cx = EditorTestContext::new(cx).await;
 2767        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2768        cx.set_state(indoc! {"
 2769        // Fooˇ
 2770    "});
 2771
 2772        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2773        cx.assert_editor_state(indoc! {"
 2774        // Foo
 2775        // ˇ
 2776    "});
 2777        // Ensure that we add comment prefix when existing line contains space
 2778        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2779        cx.assert_editor_state(
 2780            indoc! {"
 2781        // Foo
 2782        //s
 2783        // ˇ
 2784    "}
 2785            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2786            .as_str(),
 2787        );
 2788        // Ensure that we add comment prefix when existing line does not contain space
 2789        cx.set_state(indoc! {"
 2790        // Foo
 2791        //ˇ
 2792    "});
 2793        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2794        cx.assert_editor_state(indoc! {"
 2795        // Foo
 2796        //
 2797        // ˇ
 2798    "});
 2799        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2800        cx.set_state(indoc! {"
 2801        ˇ// Foo
 2802    "});
 2803        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2804        cx.assert_editor_state(indoc! {"
 2805
 2806        ˇ// Foo
 2807    "});
 2808    }
 2809    // Ensure that comment continuations can be disabled.
 2810    update_test_language_settings(cx, |settings| {
 2811        settings.defaults.extend_comment_on_newline = Some(false);
 2812    });
 2813    let mut cx = EditorTestContext::new(cx).await;
 2814    cx.set_state(indoc! {"
 2815        // Fooˇ
 2816    "});
 2817    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2818    cx.assert_editor_state(indoc! {"
 2819        // Foo
 2820        ˇ
 2821    "});
 2822}
 2823
 2824#[gpui::test]
 2825async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2826    init_test(cx, |settings| {
 2827        settings.defaults.tab_size = NonZeroU32::new(4)
 2828    });
 2829
 2830    let language = Arc::new(Language::new(
 2831        LanguageConfig {
 2832            line_comments: vec!["// ".into(), "/// ".into()],
 2833            ..LanguageConfig::default()
 2834        },
 2835        None,
 2836    ));
 2837    {
 2838        let mut cx = EditorTestContext::new(cx).await;
 2839        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2840        cx.set_state(indoc! {"
 2841        //ˇ
 2842    "});
 2843        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2844        cx.assert_editor_state(indoc! {"
 2845        //
 2846        // ˇ
 2847    "});
 2848
 2849        cx.set_state(indoc! {"
 2850        ///ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        ///
 2855        /// ˇ
 2856    "});
 2857    }
 2858}
 2859
 2860#[gpui::test]
 2861async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2862    init_test(cx, |settings| {
 2863        settings.defaults.tab_size = NonZeroU32::new(4)
 2864    });
 2865
 2866    let language = Arc::new(
 2867        Language::new(
 2868            LanguageConfig {
 2869                documentation: Some(language::DocumentationConfig {
 2870                    start: "/**".into(),
 2871                    end: "*/".into(),
 2872                    prefix: "* ".into(),
 2873                    tab_size: NonZeroU32::new(1).unwrap(),
 2874                }),
 2875
 2876                ..LanguageConfig::default()
 2877            },
 2878            Some(tree_sitter_rust::LANGUAGE.into()),
 2879        )
 2880        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2881        .unwrap(),
 2882    );
 2883
 2884    {
 2885        let mut cx = EditorTestContext::new(cx).await;
 2886        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2887        cx.set_state(indoc! {"
 2888        /**ˇ
 2889    "});
 2890
 2891        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2892        cx.assert_editor_state(indoc! {"
 2893        /**
 2894         * ˇ
 2895    "});
 2896        // Ensure that if cursor is before the comment start,
 2897        // we do not actually insert a comment prefix.
 2898        cx.set_state(indoc! {"
 2899        ˇ/**
 2900    "});
 2901        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2902        cx.assert_editor_state(indoc! {"
 2903
 2904        ˇ/**
 2905    "});
 2906        // Ensure that if cursor is between it doesn't add comment prefix.
 2907        cx.set_state(indoc! {"
 2908        /*ˇ*
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912        /*
 2913        ˇ*
 2914    "});
 2915        // Ensure that if suffix exists on same line after cursor it adds new line.
 2916        cx.set_state(indoc! {"
 2917        /**ˇ*/
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /**
 2922         * ˇ
 2923         */
 2924    "});
 2925        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2926        cx.set_state(indoc! {"
 2927        /**ˇ */
 2928    "});
 2929        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2930        cx.assert_editor_state(indoc! {"
 2931        /**
 2932         * ˇ
 2933         */
 2934    "});
 2935        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2936        cx.set_state(indoc! {"
 2937        /** ˇ*/
 2938    "});
 2939        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2940        cx.assert_editor_state(
 2941            indoc! {"
 2942        /**s
 2943         * ˇ
 2944         */
 2945    "}
 2946            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2947            .as_str(),
 2948        );
 2949        // Ensure that delimiter space is preserved when newline on already
 2950        // spaced delimiter.
 2951        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2952        cx.assert_editor_state(
 2953            indoc! {"
 2954        /**s
 2955         *s
 2956         * ˇ
 2957         */
 2958    "}
 2959            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2960            .as_str(),
 2961        );
 2962        // Ensure that delimiter space is preserved when space is not
 2963        // on existing delimiter.
 2964        cx.set_state(indoc! {"
 2965        /**
 2966 2967         */
 2968    "});
 2969        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2970        cx.assert_editor_state(indoc! {"
 2971        /**
 2972         *
 2973         * ˇ
 2974         */
 2975    "});
 2976        // Ensure that if suffix exists on same line after cursor it
 2977        // doesn't add extra new line if prefix is not on same line.
 2978        cx.set_state(indoc! {"
 2979        /**
 2980        ˇ*/
 2981    "});
 2982        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2983        cx.assert_editor_state(indoc! {"
 2984        /**
 2985
 2986        ˇ*/
 2987    "});
 2988        // Ensure that it detects suffix after existing prefix.
 2989        cx.set_state(indoc! {"
 2990        /**ˇ/
 2991    "});
 2992        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2993        cx.assert_editor_state(indoc! {"
 2994        /**
 2995        ˇ/
 2996    "});
 2997        // Ensure that if suffix exists on same line before
 2998        // cursor it does not add comment prefix.
 2999        cx.set_state(indoc! {"
 3000        /** */ˇ
 3001    "});
 3002        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3003        cx.assert_editor_state(indoc! {"
 3004        /** */
 3005        ˇ
 3006    "});
 3007        // Ensure that if suffix exists on same line before
 3008        // cursor it does not add comment prefix.
 3009        cx.set_state(indoc! {"
 3010        /**
 3011         *
 3012         */ˇ
 3013    "});
 3014        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3015        cx.assert_editor_state(indoc! {"
 3016        /**
 3017         *
 3018         */
 3019         ˇ
 3020    "});
 3021
 3022        // Ensure that inline comment followed by code
 3023        // doesn't add comment prefix on newline
 3024        cx.set_state(indoc! {"
 3025        /** */ textˇ
 3026    "});
 3027        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3028        cx.assert_editor_state(indoc! {"
 3029        /** */ text
 3030        ˇ
 3031    "});
 3032
 3033        // Ensure that text after comment end tag
 3034        // doesn't add comment prefix on newline
 3035        cx.set_state(indoc! {"
 3036        /**
 3037         *
 3038         */ˇtext
 3039    "});
 3040        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3041        cx.assert_editor_state(indoc! {"
 3042        /**
 3043         *
 3044         */
 3045         ˇtext
 3046    "});
 3047
 3048        // Ensure if not comment block it doesn't
 3049        // add comment prefix on newline
 3050        cx.set_state(indoc! {"
 3051        * textˇ
 3052    "});
 3053        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3054        cx.assert_editor_state(indoc! {"
 3055        * text
 3056        ˇ
 3057    "});
 3058    }
 3059    // Ensure that comment continuations can be disabled.
 3060    update_test_language_settings(cx, |settings| {
 3061        settings.defaults.extend_comment_on_newline = Some(false);
 3062    });
 3063    let mut cx = EditorTestContext::new(cx).await;
 3064    cx.set_state(indoc! {"
 3065        /**ˇ
 3066    "});
 3067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3068    cx.assert_editor_state(indoc! {"
 3069        /**
 3070        ˇ
 3071    "});
 3072}
 3073
 3074#[gpui::test]
 3075fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3076    init_test(cx, |_| {});
 3077
 3078    let editor = cx.add_window(|window, cx| {
 3079        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3080        let mut editor = build_editor(buffer.clone(), window, cx);
 3081        editor.change_selections(None, window, cx, |s| {
 3082            s.select_ranges([3..4, 11..12, 19..20])
 3083        });
 3084        editor
 3085    });
 3086
 3087    _ = editor.update(cx, |editor, window, cx| {
 3088        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3089        editor.buffer.update(cx, |buffer, cx| {
 3090            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3091            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3092        });
 3093        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3094
 3095        editor.insert("Z", window, cx);
 3096        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3097
 3098        // The selections are moved after the inserted characters
 3099        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3100    });
 3101}
 3102
 3103#[gpui::test]
 3104async fn test_tab(cx: &mut TestAppContext) {
 3105    init_test(cx, |settings| {
 3106        settings.defaults.tab_size = NonZeroU32::new(3)
 3107    });
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.set_state(indoc! {"
 3111        ˇabˇc
 3112        ˇ🏀ˇ🏀ˇefg
 3113 3114    "});
 3115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117           ˇab ˇc
 3118           ˇ🏀  ˇ🏀  ˇefg
 3119        d  ˇ
 3120    "});
 3121
 3122    cx.set_state(indoc! {"
 3123        a
 3124        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3125    "});
 3126    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3127    cx.assert_editor_state(indoc! {"
 3128        a
 3129           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3130    "});
 3131}
 3132
 3133#[gpui::test]
 3134async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3135    init_test(cx, |_| {});
 3136
 3137    let mut cx = EditorTestContext::new(cx).await;
 3138    let language = Arc::new(
 3139        Language::new(
 3140            LanguageConfig::default(),
 3141            Some(tree_sitter_rust::LANGUAGE.into()),
 3142        )
 3143        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3144        .unwrap(),
 3145    );
 3146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3147
 3148    // test when all cursors are not at suggested indent
 3149    // then simply move to their suggested indent location
 3150    cx.set_state(indoc! {"
 3151        const a: B = (
 3152            c(
 3153        ˇ
 3154        ˇ    )
 3155        );
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        const a: B = (
 3160            c(
 3161                ˇ
 3162            ˇ)
 3163        );
 3164    "});
 3165
 3166    // test cursor already at suggested indent not moving when
 3167    // other cursors are yet to reach their suggested indents
 3168    cx.set_state(indoc! {"
 3169        ˇ
 3170        const a: B = (
 3171            c(
 3172                d(
 3173        ˇ
 3174                )
 3175        ˇ
 3176        ˇ    )
 3177        );
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        ˇ
 3182        const a: B = (
 3183            c(
 3184                d(
 3185                    ˇ
 3186                )
 3187                ˇ
 3188            ˇ)
 3189        );
 3190    "});
 3191    // test when all cursors are at suggested indent then tab is inserted
 3192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194            ˇ
 3195        const a: B = (
 3196            c(
 3197                d(
 3198                        ˇ
 3199                )
 3200                    ˇ
 3201                ˇ)
 3202        );
 3203    "});
 3204
 3205    // test when current indent is less than suggested indent,
 3206    // we adjust line to match suggested indent and move cursor to it
 3207    //
 3208    // when no other cursor is at word boundary, all of them should move
 3209    cx.set_state(indoc! {"
 3210        const a: B = (
 3211            c(
 3212                d(
 3213        ˇ
 3214        ˇ   )
 3215        ˇ   )
 3216        );
 3217    "});
 3218    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3219    cx.assert_editor_state(indoc! {"
 3220        const a: B = (
 3221            c(
 3222                d(
 3223                    ˇ
 3224                ˇ)
 3225            ˇ)
 3226        );
 3227    "});
 3228
 3229    // test when current indent is less than suggested indent,
 3230    // we adjust line to match suggested indent and move cursor to it
 3231    //
 3232    // when some other cursor is at word boundary, it should not move
 3233    cx.set_state(indoc! {"
 3234        const a: B = (
 3235            c(
 3236                d(
 3237        ˇ
 3238        ˇ   )
 3239           ˇ)
 3240        );
 3241    "});
 3242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244        const a: B = (
 3245            c(
 3246                d(
 3247                    ˇ
 3248                ˇ)
 3249            ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is more than suggested indent,
 3254    // we just move cursor to current indent instead of suggested indent
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ                )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                        ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3277    cx.assert_editor_state(indoc! {"
 3278        const a: B = (
 3279            c(
 3280                d(
 3281                        ˇ
 3282                            ˇ)
 3283                ˇ)
 3284        );
 3285    "});
 3286
 3287    // test when current indent is more than suggested indent,
 3288    // we just move cursor to current indent instead of suggested indent
 3289    //
 3290    // when some other cursor is at word boundary, it doesn't move
 3291    cx.set_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295        ˇ
 3296        ˇ                )
 3297            ˇ)
 3298        );
 3299    "});
 3300    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3301    cx.assert_editor_state(indoc! {"
 3302        const a: B = (
 3303            c(
 3304                d(
 3305                    ˇ
 3306                        ˇ)
 3307            ˇ)
 3308        );
 3309    "});
 3310
 3311    // handle auto-indent when there are multiple cursors on the same line
 3312    cx.set_state(indoc! {"
 3313        const a: B = (
 3314            c(
 3315        ˇ    ˇ
 3316        ˇ    )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                ˇ
 3324            ˇ)
 3325        );
 3326    "});
 3327}
 3328
 3329#[gpui::test]
 3330async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3331    init_test(cx, |settings| {
 3332        settings.defaults.tab_size = NonZeroU32::new(3)
 3333    });
 3334
 3335    let mut cx = EditorTestContext::new(cx).await;
 3336    cx.set_state(indoc! {"
 3337         ˇ
 3338        \t ˇ
 3339        \t  ˇ
 3340        \t   ˇ
 3341         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3342    "});
 3343
 3344    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3345    cx.assert_editor_state(indoc! {"
 3346           ˇ
 3347        \t   ˇ
 3348        \t   ˇ
 3349        \t      ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3351    "});
 3352}
 3353
 3354#[gpui::test]
 3355async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3356    init_test(cx, |settings| {
 3357        settings.defaults.tab_size = NonZeroU32::new(4)
 3358    });
 3359
 3360    let language = Arc::new(
 3361        Language::new(
 3362            LanguageConfig::default(),
 3363            Some(tree_sitter_rust::LANGUAGE.into()),
 3364        )
 3365        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3366        .unwrap(),
 3367    );
 3368
 3369    let mut cx = EditorTestContext::new(cx).await;
 3370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3371    cx.set_state(indoc! {"
 3372        fn a() {
 3373            if b {
 3374        \t ˇc
 3375            }
 3376        }
 3377    "});
 3378
 3379    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3380    cx.assert_editor_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383                ˇc
 3384            }
 3385        }
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_indent_outdent(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4);
 3393    });
 3394
 3395    let mut cx = EditorTestContext::new(cx).await;
 3396
 3397    cx.set_state(indoc! {"
 3398          «oneˇ» «twoˇ»
 3399        three
 3400         four
 3401    "});
 3402    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3403    cx.assert_editor_state(indoc! {"
 3404            «oneˇ» «twoˇ»
 3405        three
 3406         four
 3407    "});
 3408
 3409    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3410    cx.assert_editor_state(indoc! {"
 3411        «oneˇ» «twoˇ»
 3412        three
 3413         four
 3414    "});
 3415
 3416    // select across line ending
 3417    cx.set_state(indoc! {"
 3418        one two
 3419        t«hree
 3420        ˇ» four
 3421    "});
 3422    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3423    cx.assert_editor_state(indoc! {"
 3424        one two
 3425            t«hree
 3426        ˇ» four
 3427    "});
 3428
 3429    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3430    cx.assert_editor_state(indoc! {"
 3431        one two
 3432        t«hree
 3433        ˇ» four
 3434    "});
 3435
 3436    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3437    cx.set_state(indoc! {"
 3438        one two
 3439        ˇthree
 3440            four
 3441    "});
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444        one two
 3445            ˇthree
 3446            four
 3447    "});
 3448
 3449    cx.set_state(indoc! {"
 3450        one two
 3451        ˇ    three
 3452            four
 3453    "});
 3454    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3455    cx.assert_editor_state(indoc! {"
 3456        one two
 3457        ˇthree
 3458            four
 3459    "});
 3460}
 3461
 3462#[gpui::test]
 3463async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3464    init_test(cx, |settings| {
 3465        settings.defaults.hard_tabs = Some(true);
 3466    });
 3467
 3468    let mut cx = EditorTestContext::new(cx).await;
 3469
 3470    // select two ranges on one line
 3471    cx.set_state(indoc! {"
 3472        «oneˇ» «twoˇ»
 3473        three
 3474        four
 3475    "});
 3476    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3477    cx.assert_editor_state(indoc! {"
 3478        \t«oneˇ» «twoˇ»
 3479        three
 3480        four
 3481    "});
 3482    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        \t\t«oneˇ» «twoˇ»
 3485        three
 3486        four
 3487    "});
 3488    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3489    cx.assert_editor_state(indoc! {"
 3490        \t«oneˇ» «twoˇ»
 3491        three
 3492        four
 3493    "});
 3494    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3495    cx.assert_editor_state(indoc! {"
 3496        «oneˇ» «twoˇ»
 3497        three
 3498        four
 3499    "});
 3500
 3501    // select across a line ending
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        t«hree
 3505        ˇ»four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        \tt«hree
 3511        ˇ»four
 3512    "});
 3513    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3514    cx.assert_editor_state(indoc! {"
 3515        one two
 3516        \t\tt«hree
 3517        ˇ»four
 3518    "});
 3519    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3520    cx.assert_editor_state(indoc! {"
 3521        one two
 3522        \tt«hree
 3523        ˇ»four
 3524    "});
 3525    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3526    cx.assert_editor_state(indoc! {"
 3527        one two
 3528        t«hree
 3529        ˇ»four
 3530    "});
 3531
 3532    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3533    cx.set_state(indoc! {"
 3534        one two
 3535        ˇthree
 3536        four
 3537    "});
 3538    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3539    cx.assert_editor_state(indoc! {"
 3540        one two
 3541        ˇthree
 3542        four
 3543    "});
 3544    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3545    cx.assert_editor_state(indoc! {"
 3546        one two
 3547        \tˇthree
 3548        four
 3549    "});
 3550    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3551    cx.assert_editor_state(indoc! {"
 3552        one two
 3553        ˇthree
 3554        four
 3555    "});
 3556}
 3557
 3558#[gpui::test]
 3559fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3560    init_test(cx, |settings| {
 3561        settings.languages.extend([
 3562            (
 3563                "TOML".into(),
 3564                LanguageSettingsContent {
 3565                    tab_size: NonZeroU32::new(2),
 3566                    ..Default::default()
 3567                },
 3568            ),
 3569            (
 3570                "Rust".into(),
 3571                LanguageSettingsContent {
 3572                    tab_size: NonZeroU32::new(4),
 3573                    ..Default::default()
 3574                },
 3575            ),
 3576        ]);
 3577    });
 3578
 3579    let toml_language = Arc::new(Language::new(
 3580        LanguageConfig {
 3581            name: "TOML".into(),
 3582            ..Default::default()
 3583        },
 3584        None,
 3585    ));
 3586    let rust_language = Arc::new(Language::new(
 3587        LanguageConfig {
 3588            name: "Rust".into(),
 3589            ..Default::default()
 3590        },
 3591        None,
 3592    ));
 3593
 3594    let toml_buffer =
 3595        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3596    let rust_buffer =
 3597        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3598    let multibuffer = cx.new(|cx| {
 3599        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3600        multibuffer.push_excerpts(
 3601            toml_buffer.clone(),
 3602            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3603            cx,
 3604        );
 3605        multibuffer.push_excerpts(
 3606            rust_buffer.clone(),
 3607            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3608            cx,
 3609        );
 3610        multibuffer
 3611    });
 3612
 3613    cx.add_window(|window, cx| {
 3614        let mut editor = build_editor(multibuffer, window, cx);
 3615
 3616        assert_eq!(
 3617            editor.text(cx),
 3618            indoc! {"
 3619                a = 1
 3620                b = 2
 3621
 3622                const c: usize = 3;
 3623            "}
 3624        );
 3625
 3626        select_ranges(
 3627            &mut editor,
 3628            indoc! {"
 3629                «aˇ» = 1
 3630                b = 2
 3631
 3632                «const c:ˇ» usize = 3;
 3633            "},
 3634            window,
 3635            cx,
 3636        );
 3637
 3638        editor.tab(&Tab, window, cx);
 3639        assert_text_with_selections(
 3640            &mut editor,
 3641            indoc! {"
 3642                  «aˇ» = 1
 3643                b = 2
 3644
 3645                    «const c:ˇ» usize = 3;
 3646            "},
 3647            cx,
 3648        );
 3649        editor.backtab(&Backtab, window, cx);
 3650        assert_text_with_selections(
 3651            &mut editor,
 3652            indoc! {"
 3653                «aˇ» = 1
 3654                b = 2
 3655
 3656                «const c:ˇ» usize = 3;
 3657            "},
 3658            cx,
 3659        );
 3660
 3661        editor
 3662    });
 3663}
 3664
 3665#[gpui::test]
 3666async fn test_backspace(cx: &mut TestAppContext) {
 3667    init_test(cx, |_| {});
 3668
 3669    let mut cx = EditorTestContext::new(cx).await;
 3670
 3671    // Basic backspace
 3672    cx.set_state(indoc! {"
 3673        onˇe two three
 3674        fou«rˇ» five six
 3675        seven «ˇeight nine
 3676        »ten
 3677    "});
 3678    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3679    cx.assert_editor_state(indoc! {"
 3680        oˇe two three
 3681        fouˇ five six
 3682        seven ˇten
 3683    "});
 3684
 3685    // Test backspace inside and around indents
 3686    cx.set_state(indoc! {"
 3687        zero
 3688            ˇone
 3689                ˇtwo
 3690            ˇ ˇ ˇ  three
 3691        ˇ  ˇ  four
 3692    "});
 3693    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3694    cx.assert_editor_state(indoc! {"
 3695        zero
 3696        ˇone
 3697            ˇtwo
 3698        ˇ  threeˇ  four
 3699    "});
 3700}
 3701
 3702#[gpui::test]
 3703async fn test_delete(cx: &mut TestAppContext) {
 3704    init_test(cx, |_| {});
 3705
 3706    let mut cx = EditorTestContext::new(cx).await;
 3707    cx.set_state(indoc! {"
 3708        onˇe two three
 3709        fou«rˇ» five six
 3710        seven «ˇeight nine
 3711        »ten
 3712    "});
 3713    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3714    cx.assert_editor_state(indoc! {"
 3715        onˇ two three
 3716        fouˇ five six
 3717        seven ˇten
 3718    "});
 3719}
 3720
 3721#[gpui::test]
 3722fn test_delete_line(cx: &mut TestAppContext) {
 3723    init_test(cx, |_| {});
 3724
 3725    let editor = cx.add_window(|window, cx| {
 3726        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3727        build_editor(buffer, window, cx)
 3728    });
 3729    _ = editor.update(cx, |editor, window, cx| {
 3730        editor.change_selections(None, window, cx, |s| {
 3731            s.select_display_ranges([
 3732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3733                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3734                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3735            ])
 3736        });
 3737        editor.delete_line(&DeleteLine, window, cx);
 3738        assert_eq!(editor.display_text(cx), "ghi");
 3739        assert_eq!(
 3740            editor.selections.display_ranges(cx),
 3741            vec![
 3742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3744            ]
 3745        );
 3746    });
 3747
 3748    let editor = cx.add_window(|window, cx| {
 3749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3750        build_editor(buffer, window, cx)
 3751    });
 3752    _ = editor.update(cx, |editor, window, cx| {
 3753        editor.change_selections(None, window, cx, |s| {
 3754            s.select_display_ranges([
 3755                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3756            ])
 3757        });
 3758        editor.delete_line(&DeleteLine, window, cx);
 3759        assert_eq!(editor.display_text(cx), "ghi\n");
 3760        assert_eq!(
 3761            editor.selections.display_ranges(cx),
 3762            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3763        );
 3764    });
 3765}
 3766
 3767#[gpui::test]
 3768fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3769    init_test(cx, |_| {});
 3770
 3771    cx.add_window(|window, cx| {
 3772        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3773        let mut editor = build_editor(buffer.clone(), window, cx);
 3774        let buffer = buffer.read(cx).as_singleton().unwrap();
 3775
 3776        assert_eq!(
 3777            editor.selections.ranges::<Point>(cx),
 3778            &[Point::new(0, 0)..Point::new(0, 0)]
 3779        );
 3780
 3781        // When on single line, replace newline at end by space
 3782        editor.join_lines(&JoinLines, window, cx);
 3783        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3784        assert_eq!(
 3785            editor.selections.ranges::<Point>(cx),
 3786            &[Point::new(0, 3)..Point::new(0, 3)]
 3787        );
 3788
 3789        // When multiple lines are selected, remove newlines that are spanned by the selection
 3790        editor.change_selections(None, window, cx, |s| {
 3791            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3792        });
 3793        editor.join_lines(&JoinLines, window, cx);
 3794        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3795        assert_eq!(
 3796            editor.selections.ranges::<Point>(cx),
 3797            &[Point::new(0, 11)..Point::new(0, 11)]
 3798        );
 3799
 3800        // Undo should be transactional
 3801        editor.undo(&Undo, window, cx);
 3802        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3803        assert_eq!(
 3804            editor.selections.ranges::<Point>(cx),
 3805            &[Point::new(0, 5)..Point::new(2, 2)]
 3806        );
 3807
 3808        // When joining an empty line don't insert a space
 3809        editor.change_selections(None, window, cx, |s| {
 3810            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3811        });
 3812        editor.join_lines(&JoinLines, window, cx);
 3813        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3814        assert_eq!(
 3815            editor.selections.ranges::<Point>(cx),
 3816            [Point::new(2, 3)..Point::new(2, 3)]
 3817        );
 3818
 3819        // We can remove trailing newlines
 3820        editor.join_lines(&JoinLines, window, cx);
 3821        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3822        assert_eq!(
 3823            editor.selections.ranges::<Point>(cx),
 3824            [Point::new(2, 3)..Point::new(2, 3)]
 3825        );
 3826
 3827        // We don't blow up on the last line
 3828        editor.join_lines(&JoinLines, window, cx);
 3829        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3830        assert_eq!(
 3831            editor.selections.ranges::<Point>(cx),
 3832            [Point::new(2, 3)..Point::new(2, 3)]
 3833        );
 3834
 3835        // reset to test indentation
 3836        editor.buffer.update(cx, |buffer, cx| {
 3837            buffer.edit(
 3838                [
 3839                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3840                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3841                ],
 3842                None,
 3843                cx,
 3844            )
 3845        });
 3846
 3847        // We remove any leading spaces
 3848        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3849        editor.change_selections(None, window, cx, |s| {
 3850            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3851        });
 3852        editor.join_lines(&JoinLines, window, cx);
 3853        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3854
 3855        // We don't insert a space for a line containing only spaces
 3856        editor.join_lines(&JoinLines, window, cx);
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3858
 3859        // We ignore any leading tabs
 3860        editor.join_lines(&JoinLines, window, cx);
 3861        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3862
 3863        editor
 3864    });
 3865}
 3866
 3867#[gpui::test]
 3868fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3869    init_test(cx, |_| {});
 3870
 3871    cx.add_window(|window, cx| {
 3872        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3873        let mut editor = build_editor(buffer.clone(), window, cx);
 3874        let buffer = buffer.read(cx).as_singleton().unwrap();
 3875
 3876        editor.change_selections(None, window, cx, |s| {
 3877            s.select_ranges([
 3878                Point::new(0, 2)..Point::new(1, 1),
 3879                Point::new(1, 2)..Point::new(1, 2),
 3880                Point::new(3, 1)..Point::new(3, 2),
 3881            ])
 3882        });
 3883
 3884        editor.join_lines(&JoinLines, window, cx);
 3885        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3886
 3887        assert_eq!(
 3888            editor.selections.ranges::<Point>(cx),
 3889            [
 3890                Point::new(0, 7)..Point::new(0, 7),
 3891                Point::new(1, 3)..Point::new(1, 3)
 3892            ]
 3893        );
 3894        editor
 3895    });
 3896}
 3897
 3898#[gpui::test]
 3899async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3900    init_test(cx, |_| {});
 3901
 3902    let mut cx = EditorTestContext::new(cx).await;
 3903
 3904    let diff_base = r#"
 3905        Line 0
 3906        Line 1
 3907        Line 2
 3908        Line 3
 3909        "#
 3910    .unindent();
 3911
 3912    cx.set_state(
 3913        &r#"
 3914        ˇLine 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919        .unindent(),
 3920    );
 3921
 3922    cx.set_head_text(&diff_base);
 3923    executor.run_until_parked();
 3924
 3925    // Join lines
 3926    cx.update_editor(|editor, window, cx| {
 3927        editor.join_lines(&JoinLines, window, cx);
 3928    });
 3929    executor.run_until_parked();
 3930
 3931    cx.assert_editor_state(
 3932        &r#"
 3933        Line 0ˇ Line 1
 3934        Line 2
 3935        Line 3
 3936        "#
 3937        .unindent(),
 3938    );
 3939    // Join again
 3940    cx.update_editor(|editor, window, cx| {
 3941        editor.join_lines(&JoinLines, window, cx);
 3942    });
 3943    executor.run_until_parked();
 3944
 3945    cx.assert_editor_state(
 3946        &r#"
 3947        Line 0 Line 1ˇ Line 2
 3948        Line 3
 3949        "#
 3950        .unindent(),
 3951    );
 3952}
 3953
 3954#[gpui::test]
 3955async fn test_custom_newlines_cause_no_false_positive_diffs(
 3956    executor: BackgroundExecutor,
 3957    cx: &mut TestAppContext,
 3958) {
 3959    init_test(cx, |_| {});
 3960    let mut cx = EditorTestContext::new(cx).await;
 3961    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3962    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3963    executor.run_until_parked();
 3964
 3965    cx.update_editor(|editor, window, cx| {
 3966        let snapshot = editor.snapshot(window, cx);
 3967        assert_eq!(
 3968            snapshot
 3969                .buffer_snapshot
 3970                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3971                .collect::<Vec<_>>(),
 3972            Vec::new(),
 3973            "Should not have any diffs for files with custom newlines"
 3974        );
 3975    });
 3976}
 3977
 3978#[gpui::test]
 3979async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3980    init_test(cx, |_| {});
 3981
 3982    let mut cx = EditorTestContext::new(cx).await;
 3983
 3984    // Test sort_lines_case_insensitive()
 3985    cx.set_state(indoc! {"
 3986        «z
 3987        y
 3988        x
 3989        Z
 3990        Y
 3991        Xˇ»
 3992    "});
 3993    cx.update_editor(|e, window, cx| {
 3994        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3995    });
 3996    cx.assert_editor_state(indoc! {"
 3997        «x
 3998        X
 3999        y
 4000        Y
 4001        z
 4002        Zˇ»
 4003    "});
 4004
 4005    // Test reverse_lines()
 4006    cx.set_state(indoc! {"
 4007        «5
 4008        4
 4009        3
 4010        2
 4011        1ˇ»
 4012    "});
 4013    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4014    cx.assert_editor_state(indoc! {"
 4015        «1
 4016        2
 4017        3
 4018        4
 4019        5ˇ»
 4020    "});
 4021
 4022    // Skip testing shuffle_line()
 4023
 4024    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 4025    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 4026
 4027    // Don't manipulate when cursor is on single line, but expand the selection
 4028    cx.set_state(indoc! {"
 4029        ddˇdd
 4030        ccc
 4031        bb
 4032        a
 4033    "});
 4034    cx.update_editor(|e, window, cx| {
 4035        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4036    });
 4037    cx.assert_editor_state(indoc! {"
 4038        «ddddˇ»
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043
 4044    // Basic manipulate case
 4045    // Start selection moves to column 0
 4046    // End of selection shrinks to fit shorter line
 4047    cx.set_state(indoc! {"
 4048        dd«d
 4049        ccc
 4050        bb
 4051        aaaaaˇ»
 4052    "});
 4053    cx.update_editor(|e, window, cx| {
 4054        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4055    });
 4056    cx.assert_editor_state(indoc! {"
 4057        «aaaaa
 4058        bb
 4059        ccc
 4060        dddˇ»
 4061    "});
 4062
 4063    // Manipulate case with newlines
 4064    cx.set_state(indoc! {"
 4065        dd«d
 4066        ccc
 4067
 4068        bb
 4069        aaaaa
 4070
 4071        ˇ»
 4072    "});
 4073    cx.update_editor(|e, window, cx| {
 4074        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4075    });
 4076    cx.assert_editor_state(indoc! {"
 4077        «
 4078
 4079        aaaaa
 4080        bb
 4081        ccc
 4082        dddˇ»
 4083
 4084    "});
 4085
 4086    // Adding new line
 4087    cx.set_state(indoc! {"
 4088        aa«a
 4089        bbˇ»b
 4090    "});
 4091    cx.update_editor(|e, window, cx| {
 4092        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 4093    });
 4094    cx.assert_editor_state(indoc! {"
 4095        «aaa
 4096        bbb
 4097        added_lineˇ»
 4098    "});
 4099
 4100    // Removing line
 4101    cx.set_state(indoc! {"
 4102        aa«a
 4103        bbbˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.manipulate_lines(window, cx, |lines| {
 4107            lines.pop();
 4108        })
 4109    });
 4110    cx.assert_editor_state(indoc! {"
 4111        «aaaˇ»
 4112    "});
 4113
 4114    // Removing all lines
 4115    cx.set_state(indoc! {"
 4116        aa«a
 4117        bbbˇ»
 4118    "});
 4119    cx.update_editor(|e, window, cx| {
 4120        e.manipulate_lines(window, cx, |lines| {
 4121            lines.drain(..);
 4122        })
 4123    });
 4124    cx.assert_editor_state(indoc! {"
 4125        ˇ
 4126    "});
 4127}
 4128
 4129#[gpui::test]
 4130async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4131    init_test(cx, |_| {});
 4132
 4133    let mut cx = EditorTestContext::new(cx).await;
 4134
 4135    // Consider continuous selection as single selection
 4136    cx.set_state(indoc! {"
 4137        Aaa«aa
 4138        cˇ»c«c
 4139        bb
 4140        aaaˇ»aa
 4141    "});
 4142    cx.update_editor(|e, window, cx| {
 4143        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4144    });
 4145    cx.assert_editor_state(indoc! {"
 4146        «Aaaaa
 4147        ccc
 4148        bb
 4149        aaaaaˇ»
 4150    "});
 4151
 4152    cx.set_state(indoc! {"
 4153        Aaa«aa
 4154        cˇ»c«c
 4155        bb
 4156        aaaˇ»aa
 4157    "});
 4158    cx.update_editor(|e, window, cx| {
 4159        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4160    });
 4161    cx.assert_editor_state(indoc! {"
 4162        «Aaaaa
 4163        ccc
 4164        bbˇ»
 4165    "});
 4166
 4167    // Consider non continuous selection as distinct dedup operations
 4168    cx.set_state(indoc! {"
 4169        «aaaaa
 4170        bb
 4171        aaaaa
 4172        aaaaaˇ»
 4173
 4174        aaa«aaˇ»
 4175    "});
 4176    cx.update_editor(|e, window, cx| {
 4177        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4178    });
 4179    cx.assert_editor_state(indoc! {"
 4180        «aaaaa
 4181        bbˇ»
 4182
 4183        «aaaaaˇ»
 4184    "});
 4185}
 4186
 4187#[gpui::test]
 4188async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4189    init_test(cx, |_| {});
 4190
 4191    let mut cx = EditorTestContext::new(cx).await;
 4192
 4193    cx.set_state(indoc! {"
 4194        «Aaa
 4195        aAa
 4196        Aaaˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| {
 4199        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4200    });
 4201    cx.assert_editor_state(indoc! {"
 4202        «Aaa
 4203        aAaˇ»
 4204    "});
 4205
 4206    cx.set_state(indoc! {"
 4207        «Aaa
 4208        aAa
 4209        aaAˇ»
 4210    "});
 4211    cx.update_editor(|e, window, cx| {
 4212        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4213    });
 4214    cx.assert_editor_state(indoc! {"
 4215        «Aaaˇ»
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224
 4225    // Manipulate with multiple selections on a single line
 4226    cx.set_state(indoc! {"
 4227        dd«dd
 4228        cˇ»c«c
 4229        bb
 4230        aaaˇ»aa
 4231    "});
 4232    cx.update_editor(|e, window, cx| {
 4233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4234    });
 4235    cx.assert_editor_state(indoc! {"
 4236        «aaaaa
 4237        bb
 4238        ccc
 4239        ddddˇ»
 4240    "});
 4241
 4242    // Manipulate with multiple disjoin selections
 4243    cx.set_state(indoc! {"
 4244 4245        4
 4246        3
 4247        2
 4248        1ˇ»
 4249
 4250        dd«dd
 4251        ccc
 4252        bb
 4253        aaaˇ»aa
 4254    "});
 4255    cx.update_editor(|e, window, cx| {
 4256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4257    });
 4258    cx.assert_editor_state(indoc! {"
 4259        «1
 4260        2
 4261        3
 4262        4
 4263        5ˇ»
 4264
 4265        «aaaaa
 4266        bb
 4267        ccc
 4268        ddddˇ»
 4269    "});
 4270
 4271    // Adding lines on each selection
 4272    cx.set_state(indoc! {"
 4273 4274        1ˇ»
 4275
 4276        bb«bb
 4277        aaaˇ»aa
 4278    "});
 4279    cx.update_editor(|e, window, cx| {
 4280        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 4281    });
 4282    cx.assert_editor_state(indoc! {"
 4283        «2
 4284        1
 4285        added lineˇ»
 4286
 4287        «bbbb
 4288        aaaaa
 4289        added lineˇ»
 4290    "});
 4291
 4292    // Removing lines on each selection
 4293    cx.set_state(indoc! {"
 4294 4295        1ˇ»
 4296
 4297        bb«bb
 4298        aaaˇ»aa
 4299    "});
 4300    cx.update_editor(|e, window, cx| {
 4301        e.manipulate_lines(window, cx, |lines| {
 4302            lines.pop();
 4303        })
 4304    });
 4305    cx.assert_editor_state(indoc! {"
 4306        «2ˇ»
 4307
 4308        «bbbbˇ»
 4309    "});
 4310}
 4311
 4312#[gpui::test]
 4313async fn test_toggle_case(cx: &mut TestAppContext) {
 4314    init_test(cx, |_| {});
 4315
 4316    let mut cx = EditorTestContext::new(cx).await;
 4317
 4318    // If all lower case -> upper case
 4319    cx.set_state(indoc! {"
 4320        «hello worldˇ»
 4321    "});
 4322    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4323    cx.assert_editor_state(indoc! {"
 4324        «HELLO WORLDˇ»
 4325    "});
 4326
 4327    // If all upper case -> lower case
 4328    cx.set_state(indoc! {"
 4329        «HELLO WORLDˇ»
 4330    "});
 4331    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4332    cx.assert_editor_state(indoc! {"
 4333        «hello worldˇ»
 4334    "});
 4335
 4336    // If any upper case characters are identified -> lower case
 4337    // This matches JetBrains IDEs
 4338    cx.set_state(indoc! {"
 4339        «hEllo worldˇ»
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        «hello worldˇ»
 4344    "});
 4345}
 4346
 4347#[gpui::test]
 4348async fn test_manipulate_text(cx: &mut TestAppContext) {
 4349    init_test(cx, |_| {});
 4350
 4351    let mut cx = EditorTestContext::new(cx).await;
 4352
 4353    // Test convert_to_upper_case()
 4354    cx.set_state(indoc! {"
 4355        «hello worldˇ»
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        «HELLO WORLDˇ»
 4360    "});
 4361
 4362    // Test convert_to_lower_case()
 4363    cx.set_state(indoc! {"
 4364        «HELLO WORLDˇ»
 4365    "});
 4366    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4367    cx.assert_editor_state(indoc! {"
 4368        «hello worldˇ»
 4369    "});
 4370
 4371    // Test multiple line, single selection case
 4372    cx.set_state(indoc! {"
 4373        «The quick brown
 4374        fox jumps over
 4375        the lazy dogˇ»
 4376    "});
 4377    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4378    cx.assert_editor_state(indoc! {"
 4379        «The Quick Brown
 4380        Fox Jumps Over
 4381        The Lazy Dogˇ»
 4382    "});
 4383
 4384    // Test multiple line, single selection case
 4385    cx.set_state(indoc! {"
 4386        «The quick brown
 4387        fox jumps over
 4388        the lazy dogˇ»
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «TheQuickBrown
 4395        FoxJumpsOver
 4396        TheLazyDogˇ»
 4397    "});
 4398
 4399    // From here on out, test more complex cases of manipulate_text()
 4400
 4401    // Test no selection case - should affect words cursors are in
 4402    // Cursor at beginning, middle, and end of word
 4403    cx.set_state(indoc! {"
 4404        ˇhello big beauˇtiful worldˇ
 4405    "});
 4406    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4407    cx.assert_editor_state(indoc! {"
 4408        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4409    "});
 4410
 4411    // Test multiple selections on a single line and across multiple lines
 4412    cx.set_state(indoc! {"
 4413        «Theˇ» quick «brown
 4414        foxˇ» jumps «overˇ»
 4415        the «lazyˇ» dog
 4416    "});
 4417    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4418    cx.assert_editor_state(indoc! {"
 4419        «THEˇ» quick «BROWN
 4420        FOXˇ» jumps «OVERˇ»
 4421        the «LAZYˇ» dog
 4422    "});
 4423
 4424    // Test case where text length grows
 4425    cx.set_state(indoc! {"
 4426        «tschüߡ»
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        «TSCHÜSSˇ»
 4431    "});
 4432
 4433    // Test to make sure we don't crash when text shrinks
 4434    cx.set_state(indoc! {"
 4435        aaa_bbbˇ
 4436    "});
 4437    cx.update_editor(|e, window, cx| {
 4438        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «aaaBbbˇ»
 4442    "});
 4443
 4444    // Test to make sure we all aware of the fact that each word can grow and shrink
 4445    // Final selections should be aware of this fact
 4446    cx.set_state(indoc! {"
 4447        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4448    "});
 4449    cx.update_editor(|e, window, cx| {
 4450        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4451    });
 4452    cx.assert_editor_state(indoc! {"
 4453        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4454    "});
 4455
 4456    cx.set_state(indoc! {"
 4457        «hElLo, WoRld!ˇ»
 4458    "});
 4459    cx.update_editor(|e, window, cx| {
 4460        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4461    });
 4462    cx.assert_editor_state(indoc! {"
 4463        «HeLlO, wOrLD!ˇ»
 4464    "});
 4465}
 4466
 4467#[gpui::test]
 4468fn test_duplicate_line(cx: &mut TestAppContext) {
 4469    init_test(cx, |_| {});
 4470
 4471    let editor = cx.add_window(|window, cx| {
 4472        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4473        build_editor(buffer, window, cx)
 4474    });
 4475    _ = editor.update(cx, |editor, window, cx| {
 4476        editor.change_selections(None, window, cx, |s| {
 4477            s.select_display_ranges([
 4478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4479                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4480                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4481                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4482            ])
 4483        });
 4484        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4485        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4486        assert_eq!(
 4487            editor.selections.display_ranges(cx),
 4488            vec![
 4489                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4490                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4491                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4492                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4493            ]
 4494        );
 4495    });
 4496
 4497    let editor = cx.add_window(|window, cx| {
 4498        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4499        build_editor(buffer, window, cx)
 4500    });
 4501    _ = editor.update(cx, |editor, window, cx| {
 4502        editor.change_selections(None, window, cx, |s| {
 4503            s.select_display_ranges([
 4504                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4505                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4506            ])
 4507        });
 4508        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4509        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![
 4513                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4514                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4515            ]
 4516        );
 4517    });
 4518
 4519    // With `move_upwards` the selections stay in place, except for
 4520    // the lines inserted above them
 4521    let editor = cx.add_window(|window, cx| {
 4522        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4523        build_editor(buffer, window, cx)
 4524    });
 4525    _ = editor.update(cx, |editor, window, cx| {
 4526        editor.change_selections(None, window, cx, |s| {
 4527            s.select_display_ranges([
 4528                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4529                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4530                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4531                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4532            ])
 4533        });
 4534        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4535        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4536        assert_eq!(
 4537            editor.selections.display_ranges(cx),
 4538            vec![
 4539                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4540                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4541                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4542                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4543            ]
 4544        );
 4545    });
 4546
 4547    let editor = cx.add_window(|window, cx| {
 4548        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4549        build_editor(buffer, window, cx)
 4550    });
 4551    _ = editor.update(cx, |editor, window, cx| {
 4552        editor.change_selections(None, window, cx, |s| {
 4553            s.select_display_ranges([
 4554                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4556            ])
 4557        });
 4558        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4559        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4560        assert_eq!(
 4561            editor.selections.display_ranges(cx),
 4562            vec![
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4565            ]
 4566        );
 4567    });
 4568
 4569    let editor = cx.add_window(|window, cx| {
 4570        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4571        build_editor(buffer, window, cx)
 4572    });
 4573    _ = editor.update(cx, |editor, window, cx| {
 4574        editor.change_selections(None, window, cx, |s| {
 4575            s.select_display_ranges([
 4576                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4577                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4578            ])
 4579        });
 4580        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4581        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4582        assert_eq!(
 4583            editor.selections.display_ranges(cx),
 4584            vec![
 4585                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4586                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4587            ]
 4588        );
 4589    });
 4590}
 4591
 4592#[gpui::test]
 4593fn test_move_line_up_down(cx: &mut TestAppContext) {
 4594    init_test(cx, |_| {});
 4595
 4596    let editor = cx.add_window(|window, cx| {
 4597        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4598        build_editor(buffer, window, cx)
 4599    });
 4600    _ = editor.update(cx, |editor, window, cx| {
 4601        editor.fold_creases(
 4602            vec![
 4603                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4604                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4605                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4606            ],
 4607            true,
 4608            window,
 4609            cx,
 4610        );
 4611        editor.change_selections(None, window, cx, |s| {
 4612            s.select_display_ranges([
 4613                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4614                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4615                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4616                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4617            ])
 4618        });
 4619        assert_eq!(
 4620            editor.display_text(cx),
 4621            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4622        );
 4623
 4624        editor.move_line_up(&MoveLineUp, window, cx);
 4625        assert_eq!(
 4626            editor.display_text(cx),
 4627            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4628        );
 4629        assert_eq!(
 4630            editor.selections.display_ranges(cx),
 4631            vec![
 4632                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4633                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4634                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4635                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4636            ]
 4637        );
 4638    });
 4639
 4640    _ = editor.update(cx, |editor, window, cx| {
 4641        editor.move_line_down(&MoveLineDown, window, cx);
 4642        assert_eq!(
 4643            editor.display_text(cx),
 4644            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4645        );
 4646        assert_eq!(
 4647            editor.selections.display_ranges(cx),
 4648            vec![
 4649                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4650                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4651                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4652                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4653            ]
 4654        );
 4655    });
 4656
 4657    _ = editor.update(cx, |editor, window, cx| {
 4658        editor.move_line_down(&MoveLineDown, window, cx);
 4659        assert_eq!(
 4660            editor.display_text(cx),
 4661            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4662        );
 4663        assert_eq!(
 4664            editor.selections.display_ranges(cx),
 4665            vec![
 4666                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4667                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4668                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4669                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4670            ]
 4671        );
 4672    });
 4673
 4674    _ = editor.update(cx, |editor, window, cx| {
 4675        editor.move_line_up(&MoveLineUp, window, cx);
 4676        assert_eq!(
 4677            editor.display_text(cx),
 4678            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4679        );
 4680        assert_eq!(
 4681            editor.selections.display_ranges(cx),
 4682            vec![
 4683                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4684                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4685                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4686                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4687            ]
 4688        );
 4689    });
 4690}
 4691
 4692#[gpui::test]
 4693fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let editor = cx.add_window(|window, cx| {
 4697        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4698        build_editor(buffer, window, cx)
 4699    });
 4700    _ = editor.update(cx, |editor, window, cx| {
 4701        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4702        editor.insert_blocks(
 4703            [BlockProperties {
 4704                style: BlockStyle::Fixed,
 4705                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4706                height: Some(1),
 4707                render: Arc::new(|_| div().into_any()),
 4708                priority: 0,
 4709                render_in_minimap: true,
 4710            }],
 4711            Some(Autoscroll::fit()),
 4712            cx,
 4713        );
 4714        editor.change_selections(None, window, cx, |s| {
 4715            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4716        });
 4717        editor.move_line_down(&MoveLineDown, window, cx);
 4718    });
 4719}
 4720
 4721#[gpui::test]
 4722async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4723    init_test(cx, |_| {});
 4724
 4725    let mut cx = EditorTestContext::new(cx).await;
 4726    cx.set_state(
 4727        &"
 4728            ˇzero
 4729            one
 4730            two
 4731            three
 4732            four
 4733            five
 4734        "
 4735        .unindent(),
 4736    );
 4737
 4738    // Create a four-line block that replaces three lines of text.
 4739    cx.update_editor(|editor, window, cx| {
 4740        let snapshot = editor.snapshot(window, cx);
 4741        let snapshot = &snapshot.buffer_snapshot;
 4742        let placement = BlockPlacement::Replace(
 4743            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4744        );
 4745        editor.insert_blocks(
 4746            [BlockProperties {
 4747                placement,
 4748                height: Some(4),
 4749                style: BlockStyle::Sticky,
 4750                render: Arc::new(|_| gpui::div().into_any_element()),
 4751                priority: 0,
 4752                render_in_minimap: true,
 4753            }],
 4754            None,
 4755            cx,
 4756        );
 4757    });
 4758
 4759    // Move down so that the cursor touches the block.
 4760    cx.update_editor(|editor, window, cx| {
 4761        editor.move_down(&Default::default(), window, cx);
 4762    });
 4763    cx.assert_editor_state(
 4764        &"
 4765            zero
 4766            «one
 4767            two
 4768            threeˇ»
 4769            four
 4770            five
 4771        "
 4772        .unindent(),
 4773    );
 4774
 4775    // Move down past the block.
 4776    cx.update_editor(|editor, window, cx| {
 4777        editor.move_down(&Default::default(), window, cx);
 4778    });
 4779    cx.assert_editor_state(
 4780        &"
 4781            zero
 4782            one
 4783            two
 4784            three
 4785            ˇfour
 4786            five
 4787        "
 4788        .unindent(),
 4789    );
 4790}
 4791
 4792#[gpui::test]
 4793fn test_transpose(cx: &mut TestAppContext) {
 4794    init_test(cx, |_| {});
 4795
 4796    _ = cx.add_window(|window, cx| {
 4797        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4798        editor.set_style(EditorStyle::default(), window, cx);
 4799        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4800        editor.transpose(&Default::default(), window, cx);
 4801        assert_eq!(editor.text(cx), "bac");
 4802        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4803
 4804        editor.transpose(&Default::default(), window, cx);
 4805        assert_eq!(editor.text(cx), "bca");
 4806        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4807
 4808        editor.transpose(&Default::default(), window, cx);
 4809        assert_eq!(editor.text(cx), "bac");
 4810        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4811
 4812        editor
 4813    });
 4814
 4815    _ = cx.add_window(|window, cx| {
 4816        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4817        editor.set_style(EditorStyle::default(), window, cx);
 4818        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4819        editor.transpose(&Default::default(), window, cx);
 4820        assert_eq!(editor.text(cx), "acb\nde");
 4821        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4822
 4823        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4824        editor.transpose(&Default::default(), window, cx);
 4825        assert_eq!(editor.text(cx), "acbd\ne");
 4826        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4827
 4828        editor.transpose(&Default::default(), window, cx);
 4829        assert_eq!(editor.text(cx), "acbde\n");
 4830        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4831
 4832        editor.transpose(&Default::default(), window, cx);
 4833        assert_eq!(editor.text(cx), "acbd\ne");
 4834        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4835
 4836        editor
 4837    });
 4838
 4839    _ = cx.add_window(|window, cx| {
 4840        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4841        editor.set_style(EditorStyle::default(), window, cx);
 4842        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4843        editor.transpose(&Default::default(), window, cx);
 4844        assert_eq!(editor.text(cx), "bacd\ne");
 4845        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4846
 4847        editor.transpose(&Default::default(), window, cx);
 4848        assert_eq!(editor.text(cx), "bcade\n");
 4849        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4850
 4851        editor.transpose(&Default::default(), window, cx);
 4852        assert_eq!(editor.text(cx), "bcda\ne");
 4853        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4854
 4855        editor.transpose(&Default::default(), window, cx);
 4856        assert_eq!(editor.text(cx), "bcade\n");
 4857        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4858
 4859        editor.transpose(&Default::default(), window, cx);
 4860        assert_eq!(editor.text(cx), "bcaed\n");
 4861        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4862
 4863        editor
 4864    });
 4865
 4866    _ = cx.add_window(|window, cx| {
 4867        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4868        editor.set_style(EditorStyle::default(), window, cx);
 4869        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4870        editor.transpose(&Default::default(), window, cx);
 4871        assert_eq!(editor.text(cx), "🏀🍐✋");
 4872        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4873
 4874        editor.transpose(&Default::default(), window, cx);
 4875        assert_eq!(editor.text(cx), "🏀✋🍐");
 4876        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4877
 4878        editor.transpose(&Default::default(), window, cx);
 4879        assert_eq!(editor.text(cx), "🏀🍐✋");
 4880        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4881
 4882        editor
 4883    });
 4884}
 4885
 4886#[gpui::test]
 4887async fn test_rewrap(cx: &mut TestAppContext) {
 4888    init_test(cx, |settings| {
 4889        settings.languages.extend([
 4890            (
 4891                "Markdown".into(),
 4892                LanguageSettingsContent {
 4893                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4894                    ..Default::default()
 4895                },
 4896            ),
 4897            (
 4898                "Plain Text".into(),
 4899                LanguageSettingsContent {
 4900                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4901                    ..Default::default()
 4902                },
 4903            ),
 4904        ])
 4905    });
 4906
 4907    let mut cx = EditorTestContext::new(cx).await;
 4908
 4909    let language_with_c_comments = Arc::new(Language::new(
 4910        LanguageConfig {
 4911            line_comments: vec!["// ".into()],
 4912            ..LanguageConfig::default()
 4913        },
 4914        None,
 4915    ));
 4916    let language_with_pound_comments = Arc::new(Language::new(
 4917        LanguageConfig {
 4918            line_comments: vec!["# ".into()],
 4919            ..LanguageConfig::default()
 4920        },
 4921        None,
 4922    ));
 4923    let markdown_language = Arc::new(Language::new(
 4924        LanguageConfig {
 4925            name: "Markdown".into(),
 4926            ..LanguageConfig::default()
 4927        },
 4928        None,
 4929    ));
 4930    let language_with_doc_comments = Arc::new(Language::new(
 4931        LanguageConfig {
 4932            line_comments: vec!["// ".into(), "/// ".into()],
 4933            ..LanguageConfig::default()
 4934        },
 4935        Some(tree_sitter_rust::LANGUAGE.into()),
 4936    ));
 4937
 4938    let plaintext_language = Arc::new(Language::new(
 4939        LanguageConfig {
 4940            name: "Plain Text".into(),
 4941            ..LanguageConfig::default()
 4942        },
 4943        None,
 4944    ));
 4945
 4946    assert_rewrap(
 4947        indoc! {"
 4948            // ˇ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.
 4949        "},
 4950        indoc! {"
 4951            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4952            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4953            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4954            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4955            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4956            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4957            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4958            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4959            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4960            // porttitor id. Aliquam id accumsan eros.
 4961        "},
 4962        language_with_c_comments.clone(),
 4963        &mut cx,
 4964    );
 4965
 4966    // Test that rewrapping works inside of a selection
 4967    assert_rewrap(
 4968        indoc! {"
 4969            «// 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.ˇ»
 4970        "},
 4971        indoc! {"
 4972            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4973            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4974            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4975            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4976            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4977            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4978            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4979            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4980            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4981            // porttitor id. Aliquam id accumsan eros.ˇ»
 4982        "},
 4983        language_with_c_comments.clone(),
 4984        &mut cx,
 4985    );
 4986
 4987    // Test that cursors that expand to the same region are collapsed.
 4988    assert_rewrap(
 4989        indoc! {"
 4990            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4991            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4992            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4993            // ˇ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.
 4994        "},
 4995        indoc! {"
 4996            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4997            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4998            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4999            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5000            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5001            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5002            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5003            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5004            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5005            // porttitor id. Aliquam id accumsan eros.
 5006        "},
 5007        language_with_c_comments.clone(),
 5008        &mut cx,
 5009    );
 5010
 5011    // Test that non-contiguous selections are treated separately.
 5012    assert_rewrap(
 5013        indoc! {"
 5014            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5015            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5016            //
 5017            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5018            // ˇ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.
 5019        "},
 5020        indoc! {"
 5021            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5022            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5023            // auctor, eu lacinia sapien scelerisque.
 5024            //
 5025            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5026            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5027            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5028            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5029            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5030            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5031            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5032        "},
 5033        language_with_c_comments.clone(),
 5034        &mut cx,
 5035    );
 5036
 5037    // Test that different comment prefixes are supported.
 5038    assert_rewrap(
 5039        indoc! {"
 5040            # ˇ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.
 5041        "},
 5042        indoc! {"
 5043            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5044            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5045            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5046            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5047            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5048            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5049            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5050            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5051            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5052            # accumsan eros.
 5053        "},
 5054        language_with_pound_comments.clone(),
 5055        &mut cx,
 5056    );
 5057
 5058    // Test that rewrapping is ignored outside of comments in most languages.
 5059    assert_rewrap(
 5060        indoc! {"
 5061            /// Adds two numbers.
 5062            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5063            fn add(a: u32, b: u32) -> u32 {
 5064                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ˇ
 5065            }
 5066        "},
 5067        indoc! {"
 5068            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5069            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5070            fn add(a: u32, b: u32) -> u32 {
 5071                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ˇ
 5072            }
 5073        "},
 5074        language_with_doc_comments.clone(),
 5075        &mut cx,
 5076    );
 5077
 5078    // Test that rewrapping works in Markdown and Plain Text languages.
 5079    assert_rewrap(
 5080        indoc! {"
 5081            # Hello
 5082
 5083            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.
 5084        "},
 5085        indoc! {"
 5086            # Hello
 5087
 5088            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5089            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5090            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5091            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5092            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5093            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5094            Integer sit amet scelerisque nisi.
 5095        "},
 5096        markdown_language,
 5097        &mut cx,
 5098    );
 5099
 5100    assert_rewrap(
 5101        indoc! {"
 5102            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.
 5103        "},
 5104        indoc! {"
 5105            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5106            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5107            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5108            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5109            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5110            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5111            Integer sit amet scelerisque nisi.
 5112        "},
 5113        plaintext_language,
 5114        &mut cx,
 5115    );
 5116
 5117    // Test rewrapping unaligned comments in a selection.
 5118    assert_rewrap(
 5119        indoc! {"
 5120            fn foo() {
 5121                if true {
 5122            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5123            // Praesent semper egestas tellus id dignissim.ˇ»
 5124                    do_something();
 5125                } else {
 5126                    //
 5127                }
 5128            }
 5129        "},
 5130        indoc! {"
 5131            fn foo() {
 5132                if true {
 5133            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5134                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5135                    // egestas tellus id dignissim.ˇ»
 5136                    do_something();
 5137                } else {
 5138                    //
 5139                }
 5140            }
 5141        "},
 5142        language_with_doc_comments.clone(),
 5143        &mut cx,
 5144    );
 5145
 5146    assert_rewrap(
 5147        indoc! {"
 5148            fn foo() {
 5149                if true {
 5150            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5151            // Praesent semper egestas tellus id dignissim.»
 5152                    do_something();
 5153                } else {
 5154                    //
 5155                }
 5156
 5157            }
 5158        "},
 5159        indoc! {"
 5160            fn foo() {
 5161                if true {
 5162            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5163                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5164                    // egestas tellus id dignissim.»
 5165                    do_something();
 5166                } else {
 5167                    //
 5168                }
 5169
 5170            }
 5171        "},
 5172        language_with_doc_comments.clone(),
 5173        &mut cx,
 5174    );
 5175
 5176    #[track_caller]
 5177    fn assert_rewrap(
 5178        unwrapped_text: &str,
 5179        wrapped_text: &str,
 5180        language: Arc<Language>,
 5181        cx: &mut EditorTestContext,
 5182    ) {
 5183        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5184        cx.set_state(unwrapped_text);
 5185        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5186        cx.assert_editor_state(wrapped_text);
 5187    }
 5188}
 5189
 5190#[gpui::test]
 5191async fn test_hard_wrap(cx: &mut TestAppContext) {
 5192    init_test(cx, |_| {});
 5193    let mut cx = EditorTestContext::new(cx).await;
 5194
 5195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5196    cx.update_editor(|editor, _, cx| {
 5197        editor.set_hard_wrap(Some(14), cx);
 5198    });
 5199
 5200    cx.set_state(indoc!(
 5201        "
 5202        one two three ˇ
 5203        "
 5204    ));
 5205    cx.simulate_input("four");
 5206    cx.run_until_parked();
 5207
 5208    cx.assert_editor_state(indoc!(
 5209        "
 5210        one two three
 5211        fourˇ
 5212        "
 5213    ));
 5214
 5215    cx.update_editor(|editor, window, cx| {
 5216        editor.newline(&Default::default(), window, cx);
 5217    });
 5218    cx.run_until_parked();
 5219    cx.assert_editor_state(indoc!(
 5220        "
 5221        one two three
 5222        four
 5223        ˇ
 5224        "
 5225    ));
 5226
 5227    cx.simulate_input("five");
 5228    cx.run_until_parked();
 5229    cx.assert_editor_state(indoc!(
 5230        "
 5231        one two three
 5232        four
 5233        fiveˇ
 5234        "
 5235    ));
 5236
 5237    cx.update_editor(|editor, window, cx| {
 5238        editor.newline(&Default::default(), window, cx);
 5239    });
 5240    cx.run_until_parked();
 5241    cx.simulate_input("# ");
 5242    cx.run_until_parked();
 5243    cx.assert_editor_state(indoc!(
 5244        "
 5245        one two three
 5246        four
 5247        five
 5248        # ˇ
 5249        "
 5250    ));
 5251
 5252    cx.update_editor(|editor, window, cx| {
 5253        editor.newline(&Default::default(), window, cx);
 5254    });
 5255    cx.run_until_parked();
 5256    cx.assert_editor_state(indoc!(
 5257        "
 5258        one two three
 5259        four
 5260        five
 5261        #\x20
 5262 5263        "
 5264    ));
 5265
 5266    cx.simulate_input(" 6");
 5267    cx.run_until_parked();
 5268    cx.assert_editor_state(indoc!(
 5269        "
 5270        one two three
 5271        four
 5272        five
 5273        #
 5274        # 6ˇ
 5275        "
 5276    ));
 5277}
 5278
 5279#[gpui::test]
 5280async fn test_clipboard(cx: &mut TestAppContext) {
 5281    init_test(cx, |_| {});
 5282
 5283    let mut cx = EditorTestContext::new(cx).await;
 5284
 5285    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5286    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5287    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5288
 5289    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5290    cx.set_state("two ˇfour ˇsix ˇ");
 5291    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5292    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5293
 5294    // Paste again but with only two cursors. Since the number of cursors doesn't
 5295    // match the number of slices in the clipboard, the entire clipboard text
 5296    // is pasted at each cursor.
 5297    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5298    cx.update_editor(|e, window, cx| {
 5299        e.handle_input("( ", window, cx);
 5300        e.paste(&Paste, window, cx);
 5301        e.handle_input(") ", window, cx);
 5302    });
 5303    cx.assert_editor_state(
 5304        &([
 5305            "( one✅ ",
 5306            "three ",
 5307            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5308            "three ",
 5309            "five ) ˇ",
 5310        ]
 5311        .join("\n")),
 5312    );
 5313
 5314    // Cut with three selections, one of which is full-line.
 5315    cx.set_state(indoc! {"
 5316        1«2ˇ»3
 5317        4ˇ567
 5318        «8ˇ»9"});
 5319    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5320    cx.assert_editor_state(indoc! {"
 5321        1ˇ3
 5322        ˇ9"});
 5323
 5324    // Paste with three selections, noticing how the copied selection that was full-line
 5325    // gets inserted before the second cursor.
 5326    cx.set_state(indoc! {"
 5327        1ˇ3
 5328 5329        «oˇ»ne"});
 5330    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5331    cx.assert_editor_state(indoc! {"
 5332        12ˇ3
 5333        4567
 5334 5335        8ˇne"});
 5336
 5337    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5338    cx.set_state(indoc! {"
 5339        The quick brown
 5340        fox juˇmps over
 5341        the lazy dog"});
 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("fox jumps over\n".to_string())
 5347    );
 5348
 5349    // Paste with three selections, noticing how the copied full-line selection is inserted
 5350    // before the empty selections but replaces the selection that is non-empty.
 5351    cx.set_state(indoc! {"
 5352        Tˇhe quick brown
 5353        «foˇ»x jumps over
 5354        tˇhe lazy dog"});
 5355    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5356    cx.assert_editor_state(indoc! {"
 5357        fox jumps over
 5358        Tˇhe quick brown
 5359        fox jumps over
 5360        ˇx jumps over
 5361        fox jumps over
 5362        tˇhe lazy dog"});
 5363}
 5364
 5365#[gpui::test]
 5366async fn test_copy_trim(cx: &mut TestAppContext) {
 5367    init_test(cx, |_| {});
 5368
 5369    let mut cx = EditorTestContext::new(cx).await;
 5370    cx.set_state(
 5371        r#"            «for selection in selections.iter() {
 5372            let mut start = selection.start;
 5373            let mut end = selection.end;
 5374            let is_entire_line = selection.is_empty();
 5375            if is_entire_line {
 5376                start = Point::new(start.row, 0);ˇ»
 5377                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5378            }
 5379        "#,
 5380    );
 5381    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5382    assert_eq!(
 5383        cx.read_from_clipboard()
 5384            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5385        Some(
 5386            "for selection in selections.iter() {
 5387            let mut start = selection.start;
 5388            let mut end = selection.end;
 5389            let is_entire_line = selection.is_empty();
 5390            if is_entire_line {
 5391                start = Point::new(start.row, 0);"
 5392                .to_string()
 5393        ),
 5394        "Regular copying preserves all indentation selected",
 5395    );
 5396    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5397    assert_eq!(
 5398        cx.read_from_clipboard()
 5399            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5400        Some(
 5401            "for selection in selections.iter() {
 5402let mut start = selection.start;
 5403let mut end = selection.end;
 5404let is_entire_line = selection.is_empty();
 5405if is_entire_line {
 5406    start = Point::new(start.row, 0);"
 5407                .to_string()
 5408        ),
 5409        "Copying with stripping should strip all leading whitespaces"
 5410    );
 5411
 5412    cx.set_state(
 5413        r#"       «     for selection in selections.iter() {
 5414            let mut start = selection.start;
 5415            let mut end = selection.end;
 5416            let is_entire_line = selection.is_empty();
 5417            if is_entire_line {
 5418                start = Point::new(start.row, 0);ˇ»
 5419                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5420            }
 5421        "#,
 5422    );
 5423    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5424    assert_eq!(
 5425        cx.read_from_clipboard()
 5426            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5427        Some(
 5428            "     for selection in selections.iter() {
 5429            let mut start = selection.start;
 5430            let mut end = selection.end;
 5431            let is_entire_line = selection.is_empty();
 5432            if is_entire_line {
 5433                start = Point::new(start.row, 0);"
 5434                .to_string()
 5435        ),
 5436        "Regular copying preserves all indentation selected",
 5437    );
 5438    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5439    assert_eq!(
 5440        cx.read_from_clipboard()
 5441            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5442        Some(
 5443            "for selection in selections.iter() {
 5444let mut start = selection.start;
 5445let mut end = selection.end;
 5446let is_entire_line = selection.is_empty();
 5447if is_entire_line {
 5448    start = Point::new(start.row, 0);"
 5449                .to_string()
 5450        ),
 5451        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5452    );
 5453
 5454    cx.set_state(
 5455        r#"       «ˇ     for selection in selections.iter() {
 5456            let mut start = selection.start;
 5457            let mut end = selection.end;
 5458            let is_entire_line = selection.is_empty();
 5459            if is_entire_line {
 5460                start = Point::new(start.row, 0);»
 5461                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5462            }
 5463        "#,
 5464    );
 5465    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5466    assert_eq!(
 5467        cx.read_from_clipboard()
 5468            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5469        Some(
 5470            "     for selection in selections.iter() {
 5471            let mut start = selection.start;
 5472            let mut end = selection.end;
 5473            let is_entire_line = selection.is_empty();
 5474            if is_entire_line {
 5475                start = Point::new(start.row, 0);"
 5476                .to_string()
 5477        ),
 5478        "Regular copying for reverse selection works the same",
 5479    );
 5480    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5481    assert_eq!(
 5482        cx.read_from_clipboard()
 5483            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5484        Some(
 5485            "for selection in selections.iter() {
 5486let mut start = selection.start;
 5487let mut end = selection.end;
 5488let is_entire_line = selection.is_empty();
 5489if is_entire_line {
 5490    start = Point::new(start.row, 0);"
 5491                .to_string()
 5492        ),
 5493        "Copying with stripping for reverse selection works the same"
 5494    );
 5495
 5496    cx.set_state(
 5497        r#"            for selection «in selections.iter() {
 5498            let mut start = selection.start;
 5499            let mut end = selection.end;
 5500            let is_entire_line = selection.is_empty();
 5501            if is_entire_line {
 5502                start = Point::new(start.row, 0);ˇ»
 5503                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5504            }
 5505        "#,
 5506    );
 5507    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5508    assert_eq!(
 5509        cx.read_from_clipboard()
 5510            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5511        Some(
 5512            "in selections.iter() {
 5513            let mut start = selection.start;
 5514            let mut end = selection.end;
 5515            let is_entire_line = selection.is_empty();
 5516            if is_entire_line {
 5517                start = Point::new(start.row, 0);"
 5518                .to_string()
 5519        ),
 5520        "When selecting past the indent, the copying works as usual",
 5521    );
 5522    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5523    assert_eq!(
 5524        cx.read_from_clipboard()
 5525            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5526        Some(
 5527            "in selections.iter() {
 5528            let mut start = selection.start;
 5529            let mut end = selection.end;
 5530            let is_entire_line = selection.is_empty();
 5531            if is_entire_line {
 5532                start = Point::new(start.row, 0);"
 5533                .to_string()
 5534        ),
 5535        "When selecting past the indent, nothing is trimmed"
 5536    );
 5537
 5538    cx.set_state(
 5539        r#"            «for selection in selections.iter() {
 5540            let mut start = selection.start;
 5541
 5542            let mut end = selection.end;
 5543            let is_entire_line = selection.is_empty();
 5544            if is_entire_line {
 5545                start = Point::new(start.row, 0);
 5546ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5547            }
 5548        "#,
 5549    );
 5550    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5551    assert_eq!(
 5552        cx.read_from_clipboard()
 5553            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5554        Some(
 5555            "for selection in selections.iter() {
 5556let mut start = selection.start;
 5557
 5558let mut end = selection.end;
 5559let is_entire_line = selection.is_empty();
 5560if is_entire_line {
 5561    start = Point::new(start.row, 0);
 5562"
 5563            .to_string()
 5564        ),
 5565        "Copying with stripping should ignore empty lines"
 5566    );
 5567}
 5568
 5569#[gpui::test]
 5570async fn test_paste_multiline(cx: &mut TestAppContext) {
 5571    init_test(cx, |_| {});
 5572
 5573    let mut cx = EditorTestContext::new(cx).await;
 5574    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5575
 5576    // Cut an indented block, without the leading whitespace.
 5577    cx.set_state(indoc! {"
 5578        const a: B = (
 5579            c(),
 5580            «d(
 5581                e,
 5582                f
 5583            )ˇ»
 5584        );
 5585    "});
 5586    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5587    cx.assert_editor_state(indoc! {"
 5588        const a: B = (
 5589            c(),
 5590            ˇ
 5591        );
 5592    "});
 5593
 5594    // Paste it at the same position.
 5595    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5596    cx.assert_editor_state(indoc! {"
 5597        const a: B = (
 5598            c(),
 5599            d(
 5600                e,
 5601                f
 5602 5603        );
 5604    "});
 5605
 5606    // Paste it at a line with a lower indent level.
 5607    cx.set_state(indoc! {"
 5608        ˇ
 5609        const a: B = (
 5610            c(),
 5611        );
 5612    "});
 5613    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5614    cx.assert_editor_state(indoc! {"
 5615        d(
 5616            e,
 5617            f
 5618 5619        const a: B = (
 5620            c(),
 5621        );
 5622    "});
 5623
 5624    // Cut an indented block, with the leading whitespace.
 5625    cx.set_state(indoc! {"
 5626        const a: B = (
 5627            c(),
 5628        «    d(
 5629                e,
 5630                f
 5631            )
 5632        ˇ»);
 5633    "});
 5634    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5635    cx.assert_editor_state(indoc! {"
 5636        const a: B = (
 5637            c(),
 5638        ˇ);
 5639    "});
 5640
 5641    // Paste it at the same position.
 5642    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5643    cx.assert_editor_state(indoc! {"
 5644        const a: B = (
 5645            c(),
 5646            d(
 5647                e,
 5648                f
 5649            )
 5650        ˇ);
 5651    "});
 5652
 5653    // Paste it at a line with a higher indent level.
 5654    cx.set_state(indoc! {"
 5655        const a: B = (
 5656            c(),
 5657            d(
 5658                e,
 5659 5660            )
 5661        );
 5662    "});
 5663    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5664    cx.assert_editor_state(indoc! {"
 5665        const a: B = (
 5666            c(),
 5667            d(
 5668                e,
 5669                f    d(
 5670                    e,
 5671                    f
 5672                )
 5673        ˇ
 5674            )
 5675        );
 5676    "});
 5677
 5678    // Copy an indented block, starting mid-line
 5679    cx.set_state(indoc! {"
 5680        const a: B = (
 5681            c(),
 5682            somethin«g(
 5683                e,
 5684                f
 5685            )ˇ»
 5686        );
 5687    "});
 5688    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5689
 5690    // Paste it on a line with a lower indent level
 5691    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5692    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5693    cx.assert_editor_state(indoc! {"
 5694        const a: B = (
 5695            c(),
 5696            something(
 5697                e,
 5698                f
 5699            )
 5700        );
 5701        g(
 5702            e,
 5703            f
 5704"});
 5705}
 5706
 5707#[gpui::test]
 5708async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5709    init_test(cx, |_| {});
 5710
 5711    cx.write_to_clipboard(ClipboardItem::new_string(
 5712        "    d(\n        e\n    );\n".into(),
 5713    ));
 5714
 5715    let mut cx = EditorTestContext::new(cx).await;
 5716    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5717
 5718    cx.set_state(indoc! {"
 5719        fn a() {
 5720            b();
 5721            if c() {
 5722                ˇ
 5723            }
 5724        }
 5725    "});
 5726
 5727    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5728    cx.assert_editor_state(indoc! {"
 5729        fn a() {
 5730            b();
 5731            if c() {
 5732                d(
 5733                    e
 5734                );
 5735        ˇ
 5736            }
 5737        }
 5738    "});
 5739
 5740    cx.set_state(indoc! {"
 5741        fn a() {
 5742            b();
 5743            ˇ
 5744        }
 5745    "});
 5746
 5747    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5748    cx.assert_editor_state(indoc! {"
 5749        fn a() {
 5750            b();
 5751            d(
 5752                e
 5753            );
 5754        ˇ
 5755        }
 5756    "});
 5757}
 5758
 5759#[gpui::test]
 5760fn test_select_all(cx: &mut TestAppContext) {
 5761    init_test(cx, |_| {});
 5762
 5763    let editor = cx.add_window(|window, cx| {
 5764        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5765        build_editor(buffer, window, cx)
 5766    });
 5767    _ = editor.update(cx, |editor, window, cx| {
 5768        editor.select_all(&SelectAll, window, cx);
 5769        assert_eq!(
 5770            editor.selections.display_ranges(cx),
 5771            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5772        );
 5773    });
 5774}
 5775
 5776#[gpui::test]
 5777fn test_select_line(cx: &mut TestAppContext) {
 5778    init_test(cx, |_| {});
 5779
 5780    let editor = cx.add_window(|window, cx| {
 5781        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5782        build_editor(buffer, window, cx)
 5783    });
 5784    _ = editor.update(cx, |editor, window, cx| {
 5785        editor.change_selections(None, window, cx, |s| {
 5786            s.select_display_ranges([
 5787                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5789                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5790                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5791            ])
 5792        });
 5793        editor.select_line(&SelectLine, window, cx);
 5794        assert_eq!(
 5795            editor.selections.display_ranges(cx),
 5796            vec![
 5797                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5798                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5799            ]
 5800        );
 5801    });
 5802
 5803    _ = editor.update(cx, |editor, window, cx| {
 5804        editor.select_line(&SelectLine, window, cx);
 5805        assert_eq!(
 5806            editor.selections.display_ranges(cx),
 5807            vec![
 5808                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5809                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5810            ]
 5811        );
 5812    });
 5813
 5814    _ = editor.update(cx, |editor, window, cx| {
 5815        editor.select_line(&SelectLine, window, cx);
 5816        assert_eq!(
 5817            editor.selections.display_ranges(cx),
 5818            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5819        );
 5820    });
 5821}
 5822
 5823#[gpui::test]
 5824async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5825    init_test(cx, |_| {});
 5826    let mut cx = EditorTestContext::new(cx).await;
 5827
 5828    #[track_caller]
 5829    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5830        cx.set_state(initial_state);
 5831        cx.update_editor(|e, window, cx| {
 5832            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5833        });
 5834        cx.assert_editor_state(expected_state);
 5835    }
 5836
 5837    // Selection starts and ends at the middle of lines, left-to-right
 5838    test(
 5839        &mut cx,
 5840        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5841        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5842    );
 5843    // Same thing, right-to-left
 5844    test(
 5845        &mut cx,
 5846        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5847        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5848    );
 5849
 5850    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5851    test(
 5852        &mut cx,
 5853        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5854        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5855    );
 5856    // Same thing, right-to-left
 5857    test(
 5858        &mut cx,
 5859        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5860        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5861    );
 5862
 5863    // Whole buffer, left-to-right, last line ends with newline
 5864    test(
 5865        &mut cx,
 5866        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5867        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5868    );
 5869    // Same thing, right-to-left
 5870    test(
 5871        &mut cx,
 5872        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5873        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5874    );
 5875
 5876    // Starts at the end of a line, ends at the start of another
 5877    test(
 5878        &mut cx,
 5879        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5880        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5881    );
 5882}
 5883
 5884#[gpui::test]
 5885async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5886    init_test(cx, |_| {});
 5887
 5888    let editor = cx.add_window(|window, cx| {
 5889        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5890        build_editor(buffer, window, cx)
 5891    });
 5892
 5893    // setup
 5894    _ = editor.update(cx, |editor, window, cx| {
 5895        editor.fold_creases(
 5896            vec![
 5897                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5898                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5899                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5900            ],
 5901            true,
 5902            window,
 5903            cx,
 5904        );
 5905        assert_eq!(
 5906            editor.display_text(cx),
 5907            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5908        );
 5909    });
 5910
 5911    _ = editor.update(cx, |editor, window, cx| {
 5912        editor.change_selections(None, window, cx, |s| {
 5913            s.select_display_ranges([
 5914                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5915                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5916                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5917                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5918            ])
 5919        });
 5920        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5921        assert_eq!(
 5922            editor.display_text(cx),
 5923            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5924        );
 5925    });
 5926    EditorTestContext::for_editor(editor, cx)
 5927        .await
 5928        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5929
 5930    _ = editor.update(cx, |editor, window, cx| {
 5931        editor.change_selections(None, window, cx, |s| {
 5932            s.select_display_ranges([
 5933                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5934            ])
 5935        });
 5936        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5937        assert_eq!(
 5938            editor.display_text(cx),
 5939            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5940        );
 5941        assert_eq!(
 5942            editor.selections.display_ranges(cx),
 5943            [
 5944                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5945                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5946                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5947                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5948                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5949                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5950                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5951            ]
 5952        );
 5953    });
 5954    EditorTestContext::for_editor(editor, cx)
 5955        .await
 5956        .assert_editor_state(
 5957            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5958        );
 5959}
 5960
 5961#[gpui::test]
 5962async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5963    init_test(cx, |_| {});
 5964
 5965    let mut cx = EditorTestContext::new(cx).await;
 5966
 5967    cx.set_state(indoc!(
 5968        r#"abc
 5969           defˇghi
 5970
 5971           jk
 5972           nlmo
 5973           "#
 5974    ));
 5975
 5976    cx.update_editor(|editor, window, cx| {
 5977        editor.add_selection_above(&Default::default(), window, cx);
 5978    });
 5979
 5980    cx.assert_editor_state(indoc!(
 5981        r#"abcˇ
 5982           defˇghi
 5983
 5984           jk
 5985           nlmo
 5986           "#
 5987    ));
 5988
 5989    cx.update_editor(|editor, window, cx| {
 5990        editor.add_selection_above(&Default::default(), window, cx);
 5991    });
 5992
 5993    cx.assert_editor_state(indoc!(
 5994        r#"abcˇ
 5995            defˇghi
 5996
 5997            jk
 5998            nlmo
 5999            "#
 6000    ));
 6001
 6002    cx.update_editor(|editor, window, cx| {
 6003        editor.add_selection_below(&Default::default(), window, cx);
 6004    });
 6005
 6006    cx.assert_editor_state(indoc!(
 6007        r#"abc
 6008           defˇghi
 6009
 6010           jk
 6011           nlmo
 6012           "#
 6013    ));
 6014
 6015    cx.update_editor(|editor, window, cx| {
 6016        editor.undo_selection(&Default::default(), window, cx);
 6017    });
 6018
 6019    cx.assert_editor_state(indoc!(
 6020        r#"abcˇ
 6021           defˇghi
 6022
 6023           jk
 6024           nlmo
 6025           "#
 6026    ));
 6027
 6028    cx.update_editor(|editor, window, cx| {
 6029        editor.redo_selection(&Default::default(), window, cx);
 6030    });
 6031
 6032    cx.assert_editor_state(indoc!(
 6033        r#"abc
 6034           defˇghi
 6035
 6036           jk
 6037           nlmo
 6038           "#
 6039    ));
 6040
 6041    cx.update_editor(|editor, window, cx| {
 6042        editor.add_selection_below(&Default::default(), window, cx);
 6043    });
 6044
 6045    cx.assert_editor_state(indoc!(
 6046        r#"abc
 6047           defˇghi
 6048           ˇ
 6049           jk
 6050           nlmo
 6051           "#
 6052    ));
 6053
 6054    cx.update_editor(|editor, window, cx| {
 6055        editor.add_selection_below(&Default::default(), window, cx);
 6056    });
 6057
 6058    cx.assert_editor_state(indoc!(
 6059        r#"abc
 6060           defˇghi
 6061           ˇ
 6062           jkˇ
 6063           nlmo
 6064           "#
 6065    ));
 6066
 6067    cx.update_editor(|editor, window, cx| {
 6068        editor.add_selection_below(&Default::default(), window, cx);
 6069    });
 6070
 6071    cx.assert_editor_state(indoc!(
 6072        r#"abc
 6073           defˇghi
 6074           ˇ
 6075           jkˇ
 6076           nlmˇo
 6077           "#
 6078    ));
 6079
 6080    cx.update_editor(|editor, window, cx| {
 6081        editor.add_selection_below(&Default::default(), window, cx);
 6082    });
 6083
 6084    cx.assert_editor_state(indoc!(
 6085        r#"abc
 6086           defˇghi
 6087           ˇ
 6088           jkˇ
 6089           nlmˇo
 6090           ˇ"#
 6091    ));
 6092
 6093    // change selections
 6094    cx.set_state(indoc!(
 6095        r#"abc
 6096           def«ˇg»hi
 6097
 6098           jk
 6099           nlmo
 6100           "#
 6101    ));
 6102
 6103    cx.update_editor(|editor, window, cx| {
 6104        editor.add_selection_below(&Default::default(), window, cx);
 6105    });
 6106
 6107    cx.assert_editor_state(indoc!(
 6108        r#"abc
 6109           def«ˇg»hi
 6110
 6111           jk
 6112           nlm«ˇo»
 6113           "#
 6114    ));
 6115
 6116    cx.update_editor(|editor, window, cx| {
 6117        editor.add_selection_below(&Default::default(), window, cx);
 6118    });
 6119
 6120    cx.assert_editor_state(indoc!(
 6121        r#"abc
 6122           def«ˇg»hi
 6123
 6124           jk
 6125           nlm«ˇo»
 6126           "#
 6127    ));
 6128
 6129    cx.update_editor(|editor, window, cx| {
 6130        editor.add_selection_above(&Default::default(), window, cx);
 6131    });
 6132
 6133    cx.assert_editor_state(indoc!(
 6134        r#"abc
 6135           def«ˇg»hi
 6136
 6137           jk
 6138           nlmo
 6139           "#
 6140    ));
 6141
 6142    cx.update_editor(|editor, window, cx| {
 6143        editor.add_selection_above(&Default::default(), window, cx);
 6144    });
 6145
 6146    cx.assert_editor_state(indoc!(
 6147        r#"abc
 6148           def«ˇg»hi
 6149
 6150           jk
 6151           nlmo
 6152           "#
 6153    ));
 6154
 6155    // Change selections again
 6156    cx.set_state(indoc!(
 6157        r#"a«bc
 6158           defgˇ»hi
 6159
 6160           jk
 6161           nlmo
 6162           "#
 6163    ));
 6164
 6165    cx.update_editor(|editor, window, cx| {
 6166        editor.add_selection_below(&Default::default(), window, cx);
 6167    });
 6168
 6169    cx.assert_editor_state(indoc!(
 6170        r#"a«bcˇ»
 6171           d«efgˇ»hi
 6172
 6173           j«kˇ»
 6174           nlmo
 6175           "#
 6176    ));
 6177
 6178    cx.update_editor(|editor, window, cx| {
 6179        editor.add_selection_below(&Default::default(), window, cx);
 6180    });
 6181    cx.assert_editor_state(indoc!(
 6182        r#"a«bcˇ»
 6183           d«efgˇ»hi
 6184
 6185           j«kˇ»
 6186           n«lmoˇ»
 6187           "#
 6188    ));
 6189    cx.update_editor(|editor, window, cx| {
 6190        editor.add_selection_above(&Default::default(), window, cx);
 6191    });
 6192
 6193    cx.assert_editor_state(indoc!(
 6194        r#"a«bcˇ»
 6195           d«efgˇ»hi
 6196
 6197           j«kˇ»
 6198           nlmo
 6199           "#
 6200    ));
 6201
 6202    // Change selections again
 6203    cx.set_state(indoc!(
 6204        r#"abc
 6205           d«ˇefghi
 6206
 6207           jk
 6208           nlm»o
 6209           "#
 6210    ));
 6211
 6212    cx.update_editor(|editor, window, cx| {
 6213        editor.add_selection_above(&Default::default(), window, cx);
 6214    });
 6215
 6216    cx.assert_editor_state(indoc!(
 6217        r#"a«ˇbc»
 6218           d«ˇef»ghi
 6219
 6220           j«ˇk»
 6221           n«ˇlm»o
 6222           "#
 6223    ));
 6224
 6225    cx.update_editor(|editor, window, cx| {
 6226        editor.add_selection_below(&Default::default(), window, cx);
 6227    });
 6228
 6229    cx.assert_editor_state(indoc!(
 6230        r#"abc
 6231           d«ˇef»ghi
 6232
 6233           j«ˇk»
 6234           n«ˇlm»o
 6235           "#
 6236    ));
 6237}
 6238
 6239#[gpui::test]
 6240async fn test_select_next(cx: &mut TestAppContext) {
 6241    init_test(cx, |_| {});
 6242
 6243    let mut cx = EditorTestContext::new(cx).await;
 6244    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6245
 6246    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6247        .unwrap();
 6248    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6249
 6250    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6251        .unwrap();
 6252    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6253
 6254    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6255    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6256
 6257    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6258    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6259
 6260    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6261        .unwrap();
 6262    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6263
 6264    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6265        .unwrap();
 6266    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6267
 6268    // Test selection direction should be preserved
 6269    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6270
 6271    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6272        .unwrap();
 6273    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6274}
 6275
 6276#[gpui::test]
 6277async fn test_select_all_matches(cx: &mut TestAppContext) {
 6278    init_test(cx, |_| {});
 6279
 6280    let mut cx = EditorTestContext::new(cx).await;
 6281
 6282    // Test caret-only selections
 6283    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6284    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6285        .unwrap();
 6286    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6287
 6288    // Test left-to-right selections
 6289    cx.set_state("abc\n«abcˇ»\nabc");
 6290    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6291        .unwrap();
 6292    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6293
 6294    // Test right-to-left selections
 6295    cx.set_state("abc\n«ˇabc»\nabc");
 6296    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6297        .unwrap();
 6298    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6299
 6300    // Test selecting whitespace with caret selection
 6301    cx.set_state("abc\nˇ   abc\nabc");
 6302    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6303        .unwrap();
 6304    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6305
 6306    // Test selecting whitespace with left-to-right selection
 6307    cx.set_state("abc\n«ˇ  »abc\nabc");
 6308    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6309        .unwrap();
 6310    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6311
 6312    // Test no matches with right-to-left selection
 6313    cx.set_state("abc\n«  ˇ»abc\nabc");
 6314    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6315        .unwrap();
 6316    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6317}
 6318
 6319#[gpui::test]
 6320async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6321    init_test(cx, |_| {});
 6322
 6323    let mut cx = EditorTestContext::new(cx).await;
 6324
 6325    let large_body_1 = "\nd".repeat(200);
 6326    let large_body_2 = "\ne".repeat(200);
 6327
 6328    cx.set_state(&format!(
 6329        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6330    ));
 6331    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6332        let scroll_position = editor.scroll_position(cx);
 6333        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6334        scroll_position
 6335    });
 6336
 6337    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6338        .unwrap();
 6339    cx.assert_editor_state(&format!(
 6340        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6341    ));
 6342    let scroll_position_after_selection =
 6343        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6344    assert_eq!(
 6345        initial_scroll_position, scroll_position_after_selection,
 6346        "Scroll position should not change after selecting all matches"
 6347    );
 6348}
 6349
 6350#[gpui::test]
 6351async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353
 6354    let mut cx = EditorLspTestContext::new_rust(
 6355        lsp::ServerCapabilities {
 6356            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6357            ..Default::default()
 6358        },
 6359        cx,
 6360    )
 6361    .await;
 6362
 6363    cx.set_state(indoc! {"
 6364        line 1
 6365        line 2
 6366        linˇe 3
 6367        line 4
 6368        line 5
 6369    "});
 6370
 6371    // Make an edit
 6372    cx.update_editor(|editor, window, cx| {
 6373        editor.handle_input("X", window, cx);
 6374    });
 6375
 6376    // Move cursor to a different position
 6377    cx.update_editor(|editor, window, cx| {
 6378        editor.change_selections(None, window, cx, |s| {
 6379            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6380        });
 6381    });
 6382
 6383    cx.assert_editor_state(indoc! {"
 6384        line 1
 6385        line 2
 6386        linXe 3
 6387        line 4
 6388        liˇne 5
 6389    "});
 6390
 6391    cx.lsp
 6392        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6393            Ok(Some(vec![lsp::TextEdit::new(
 6394                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6395                "PREFIX ".to_string(),
 6396            )]))
 6397        });
 6398
 6399    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6400        .unwrap()
 6401        .await
 6402        .unwrap();
 6403
 6404    cx.assert_editor_state(indoc! {"
 6405        PREFIX line 1
 6406        line 2
 6407        linXe 3
 6408        line 4
 6409        liˇne 5
 6410    "});
 6411
 6412    // Undo formatting
 6413    cx.update_editor(|editor, window, cx| {
 6414        editor.undo(&Default::default(), window, cx);
 6415    });
 6416
 6417    // Verify cursor moved back to position after edit
 6418    cx.assert_editor_state(indoc! {"
 6419        line 1
 6420        line 2
 6421        linXˇe 3
 6422        line 4
 6423        line 5
 6424    "});
 6425}
 6426
 6427#[gpui::test]
 6428async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6429    init_test(cx, |_| {});
 6430
 6431    let mut cx = EditorTestContext::new(cx).await;
 6432
 6433    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6434    cx.update_editor(|editor, window, cx| {
 6435        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6436    });
 6437
 6438    cx.set_state(indoc! {"
 6439        line 1
 6440        line 2
 6441        linˇe 3
 6442        line 4
 6443        line 5
 6444        line 6
 6445        line 7
 6446        line 8
 6447        line 9
 6448        line 10
 6449    "});
 6450
 6451    let snapshot = cx.buffer_snapshot();
 6452    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6453
 6454    cx.update(|_, cx| {
 6455        provider.update(cx, |provider, _| {
 6456            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6457                id: None,
 6458                edits: vec![(edit_position..edit_position, "X".into())],
 6459                edit_preview: None,
 6460            }))
 6461        })
 6462    });
 6463
 6464    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6465    cx.update_editor(|editor, window, cx| {
 6466        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6467    });
 6468
 6469    cx.assert_editor_state(indoc! {"
 6470        line 1
 6471        line 2
 6472        lineXˇ 3
 6473        line 4
 6474        line 5
 6475        line 6
 6476        line 7
 6477        line 8
 6478        line 9
 6479        line 10
 6480    "});
 6481
 6482    cx.update_editor(|editor, window, cx| {
 6483        editor.change_selections(None, window, cx, |s| {
 6484            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6485        });
 6486    });
 6487
 6488    cx.assert_editor_state(indoc! {"
 6489        line 1
 6490        line 2
 6491        lineX 3
 6492        line 4
 6493        line 5
 6494        line 6
 6495        line 7
 6496        line 8
 6497        line 9
 6498        liˇne 10
 6499    "});
 6500
 6501    cx.update_editor(|editor, window, cx| {
 6502        editor.undo(&Default::default(), window, cx);
 6503    });
 6504
 6505    cx.assert_editor_state(indoc! {"
 6506        line 1
 6507        line 2
 6508        lineˇ 3
 6509        line 4
 6510        line 5
 6511        line 6
 6512        line 7
 6513        line 8
 6514        line 9
 6515        line 10
 6516    "});
 6517}
 6518
 6519#[gpui::test]
 6520async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6521    init_test(cx, |_| {});
 6522
 6523    let mut cx = EditorTestContext::new(cx).await;
 6524    cx.set_state(
 6525        r#"let foo = 2;
 6526lˇet foo = 2;
 6527let fooˇ = 2;
 6528let foo = 2;
 6529let foo = ˇ2;"#,
 6530    );
 6531
 6532    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6533        .unwrap();
 6534    cx.assert_editor_state(
 6535        r#"let foo = 2;
 6536«letˇ» foo = 2;
 6537let «fooˇ» = 2;
 6538let foo = 2;
 6539let foo = «2ˇ»;"#,
 6540    );
 6541
 6542    // noop for multiple selections with different contents
 6543    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6544        .unwrap();
 6545    cx.assert_editor_state(
 6546        r#"let foo = 2;
 6547«letˇ» foo = 2;
 6548let «fooˇ» = 2;
 6549let foo = 2;
 6550let foo = «2ˇ»;"#,
 6551    );
 6552
 6553    // Test last selection direction should be preserved
 6554    cx.set_state(
 6555        r#"let foo = 2;
 6556let foo = 2;
 6557let «fooˇ» = 2;
 6558let «ˇfoo» = 2;
 6559let foo = 2;"#,
 6560    );
 6561
 6562    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6563        .unwrap();
 6564    cx.assert_editor_state(
 6565        r#"let foo = 2;
 6566let foo = 2;
 6567let «fooˇ» = 2;
 6568let «ˇfoo» = 2;
 6569let «ˇfoo» = 2;"#,
 6570    );
 6571}
 6572
 6573#[gpui::test]
 6574async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6575    init_test(cx, |_| {});
 6576
 6577    let mut cx =
 6578        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6579
 6580    cx.assert_editor_state(indoc! {"
 6581        ˇbbb
 6582        ccc
 6583
 6584        bbb
 6585        ccc
 6586        "});
 6587    cx.dispatch_action(SelectPrevious::default());
 6588    cx.assert_editor_state(indoc! {"
 6589                «bbbˇ»
 6590                ccc
 6591
 6592                bbb
 6593                ccc
 6594                "});
 6595    cx.dispatch_action(SelectPrevious::default());
 6596    cx.assert_editor_state(indoc! {"
 6597                «bbbˇ»
 6598                ccc
 6599
 6600                «bbbˇ»
 6601                ccc
 6602                "});
 6603}
 6604
 6605#[gpui::test]
 6606async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6607    init_test(cx, |_| {});
 6608
 6609    let mut cx = EditorTestContext::new(cx).await;
 6610    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6611
 6612    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6613        .unwrap();
 6614    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6615
 6616    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6617        .unwrap();
 6618    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6619
 6620    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6621    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6622
 6623    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6624    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6625
 6626    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6627        .unwrap();
 6628    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6629
 6630    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6631        .unwrap();
 6632    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6633}
 6634
 6635#[gpui::test]
 6636async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6637    init_test(cx, |_| {});
 6638
 6639    let mut cx = EditorTestContext::new(cx).await;
 6640    cx.set_state("");
 6641
 6642    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6643        .unwrap();
 6644    cx.assert_editor_state("«aˇ»");
 6645    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6646        .unwrap();
 6647    cx.assert_editor_state("«aˇ»");
 6648}
 6649
 6650#[gpui::test]
 6651async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 6652    init_test(cx, |_| {});
 6653
 6654    let mut cx = EditorTestContext::new(cx).await;
 6655    cx.set_state(
 6656        r#"let foo = 2;
 6657lˇet foo = 2;
 6658let fooˇ = 2;
 6659let foo = 2;
 6660let foo = ˇ2;"#,
 6661    );
 6662
 6663    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6664        .unwrap();
 6665    cx.assert_editor_state(
 6666        r#"let foo = 2;
 6667«letˇ» foo = 2;
 6668let «fooˇ» = 2;
 6669let foo = 2;
 6670let foo = «2ˇ»;"#,
 6671    );
 6672
 6673    // noop for multiple selections with different contents
 6674    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6675        .unwrap();
 6676    cx.assert_editor_state(
 6677        r#"let foo = 2;
 6678«letˇ» foo = 2;
 6679let «fooˇ» = 2;
 6680let foo = 2;
 6681let foo = «2ˇ»;"#,
 6682    );
 6683}
 6684
 6685#[gpui::test]
 6686async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 6687    init_test(cx, |_| {});
 6688
 6689    let mut cx = EditorTestContext::new(cx).await;
 6690    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6691
 6692    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6693        .unwrap();
 6694    // selection direction is preserved
 6695    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6696
 6697    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6698        .unwrap();
 6699    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6700
 6701    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6702    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 6703
 6704    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6705    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 6706
 6707    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6708        .unwrap();
 6709    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 6710
 6711    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6712        .unwrap();
 6713    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 6714}
 6715
 6716#[gpui::test]
 6717async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 6718    init_test(cx, |_| {});
 6719
 6720    let language = Arc::new(Language::new(
 6721        LanguageConfig::default(),
 6722        Some(tree_sitter_rust::LANGUAGE.into()),
 6723    ));
 6724
 6725    let text = r#"
 6726        use mod1::mod2::{mod3, mod4};
 6727
 6728        fn fn_1(param1: bool, param2: &str) {
 6729            let var1 = "text";
 6730        }
 6731    "#
 6732    .unindent();
 6733
 6734    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6737
 6738    editor
 6739        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6740        .await;
 6741
 6742    editor.update_in(cx, |editor, window, cx| {
 6743        editor.change_selections(None, window, cx, |s| {
 6744            s.select_display_ranges([
 6745                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6746                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6747                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6748            ]);
 6749        });
 6750        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6751    });
 6752    editor.update(cx, |editor, cx| {
 6753        assert_text_with_selections(
 6754            editor,
 6755            indoc! {r#"
 6756                use mod1::mod2::{mod3, «mod4ˇ»};
 6757
 6758                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6759                    let var1 = "«ˇtext»";
 6760                }
 6761            "#},
 6762            cx,
 6763        );
 6764    });
 6765
 6766    editor.update_in(cx, |editor, window, cx| {
 6767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6768    });
 6769    editor.update(cx, |editor, cx| {
 6770        assert_text_with_selections(
 6771            editor,
 6772            indoc! {r#"
 6773                use mod1::mod2::«{mod3, mod4}ˇ»;
 6774
 6775                «ˇfn fn_1(param1: bool, param2: &str) {
 6776                    let var1 = "text";
 6777 6778            "#},
 6779            cx,
 6780        );
 6781    });
 6782
 6783    editor.update_in(cx, |editor, window, cx| {
 6784        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6785    });
 6786    assert_eq!(
 6787        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6788        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6789    );
 6790
 6791    // Trying to expand the selected syntax node one more time has no effect.
 6792    editor.update_in(cx, |editor, window, cx| {
 6793        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6794    });
 6795    assert_eq!(
 6796        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6797        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6798    );
 6799
 6800    editor.update_in(cx, |editor, window, cx| {
 6801        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6802    });
 6803    editor.update(cx, |editor, cx| {
 6804        assert_text_with_selections(
 6805            editor,
 6806            indoc! {r#"
 6807                use mod1::mod2::«{mod3, mod4}ˇ»;
 6808
 6809                «ˇfn fn_1(param1: bool, param2: &str) {
 6810                    let var1 = "text";
 6811 6812            "#},
 6813            cx,
 6814        );
 6815    });
 6816
 6817    editor.update_in(cx, |editor, window, cx| {
 6818        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6819    });
 6820    editor.update(cx, |editor, cx| {
 6821        assert_text_with_selections(
 6822            editor,
 6823            indoc! {r#"
 6824                use mod1::mod2::{mod3, «mod4ˇ»};
 6825
 6826                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6827                    let var1 = "«ˇtext»";
 6828                }
 6829            "#},
 6830            cx,
 6831        );
 6832    });
 6833
 6834    editor.update_in(cx, |editor, window, cx| {
 6835        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6836    });
 6837    editor.update(cx, |editor, cx| {
 6838        assert_text_with_selections(
 6839            editor,
 6840            indoc! {r#"
 6841                use mod1::mod2::{mod3, mo«ˇ»d4};
 6842
 6843                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6844                    let var1 = "te«ˇ»xt";
 6845                }
 6846            "#},
 6847            cx,
 6848        );
 6849    });
 6850
 6851    // Trying to shrink the selected syntax node one more time has no effect.
 6852    editor.update_in(cx, |editor, window, cx| {
 6853        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6854    });
 6855    editor.update_in(cx, |editor, _, cx| {
 6856        assert_text_with_selections(
 6857            editor,
 6858            indoc! {r#"
 6859                use mod1::mod2::{mod3, mo«ˇ»d4};
 6860
 6861                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6862                    let var1 = "te«ˇ»xt";
 6863                }
 6864            "#},
 6865            cx,
 6866        );
 6867    });
 6868
 6869    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6870    // a fold.
 6871    editor.update_in(cx, |editor, window, cx| {
 6872        editor.fold_creases(
 6873            vec![
 6874                Crease::simple(
 6875                    Point::new(0, 21)..Point::new(0, 24),
 6876                    FoldPlaceholder::test(),
 6877                ),
 6878                Crease::simple(
 6879                    Point::new(3, 20)..Point::new(3, 22),
 6880                    FoldPlaceholder::test(),
 6881                ),
 6882            ],
 6883            true,
 6884            window,
 6885            cx,
 6886        );
 6887        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6888    });
 6889    editor.update(cx, |editor, cx| {
 6890        assert_text_with_selections(
 6891            editor,
 6892            indoc! {r#"
 6893                use mod1::mod2::«{mod3, mod4}ˇ»;
 6894
 6895                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6896                    let var1 = "«ˇtext»";
 6897                }
 6898            "#},
 6899            cx,
 6900        );
 6901    });
 6902}
 6903
 6904#[gpui::test]
 6905async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 6906    init_test(cx, |_| {});
 6907
 6908    let language = Arc::new(Language::new(
 6909        LanguageConfig::default(),
 6910        Some(tree_sitter_rust::LANGUAGE.into()),
 6911    ));
 6912
 6913    let text = "let a = 2;";
 6914
 6915    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6916    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6917    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6918
 6919    editor
 6920        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6921        .await;
 6922
 6923    // Test case 1: Cursor at end of word
 6924    editor.update_in(cx, |editor, window, cx| {
 6925        editor.change_selections(None, window, cx, |s| {
 6926            s.select_display_ranges([
 6927                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 6928            ]);
 6929        });
 6930    });
 6931    editor.update(cx, |editor, cx| {
 6932        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 6933    });
 6934    editor.update_in(cx, |editor, window, cx| {
 6935        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6936    });
 6937    editor.update(cx, |editor, cx| {
 6938        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 6939    });
 6940    editor.update_in(cx, |editor, window, cx| {
 6941        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6942    });
 6943    editor.update(cx, |editor, cx| {
 6944        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6945    });
 6946
 6947    // Test case 2: Cursor at end of statement
 6948    editor.update_in(cx, |editor, window, cx| {
 6949        editor.change_selections(None, window, cx, |s| {
 6950            s.select_display_ranges([
 6951                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 6952            ]);
 6953        });
 6954    });
 6955    editor.update(cx, |editor, cx| {
 6956        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 6957    });
 6958    editor.update_in(cx, |editor, window, cx| {
 6959        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6960    });
 6961    editor.update(cx, |editor, cx| {
 6962        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 6963    });
 6964}
 6965
 6966#[gpui::test]
 6967async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 6968    init_test(cx, |_| {});
 6969
 6970    let language = Arc::new(Language::new(
 6971        LanguageConfig::default(),
 6972        Some(tree_sitter_rust::LANGUAGE.into()),
 6973    ));
 6974
 6975    let text = r#"
 6976        use mod1::mod2::{mod3, mod4};
 6977
 6978        fn fn_1(param1: bool, param2: &str) {
 6979            let var1 = "hello world";
 6980        }
 6981    "#
 6982    .unindent();
 6983
 6984    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6985    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6986    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6987
 6988    editor
 6989        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6990        .await;
 6991
 6992    // Test 1: Cursor on a letter of a string word
 6993    editor.update_in(cx, |editor, window, cx| {
 6994        editor.change_selections(None, window, cx, |s| {
 6995            s.select_display_ranges([
 6996                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 6997            ]);
 6998        });
 6999    });
 7000    editor.update_in(cx, |editor, window, cx| {
 7001        assert_text_with_selections(
 7002            editor,
 7003            indoc! {r#"
 7004                use mod1::mod2::{mod3, mod4};
 7005
 7006                fn fn_1(param1: bool, param2: &str) {
 7007                    let var1 = "hˇello world";
 7008                }
 7009            "#},
 7010            cx,
 7011        );
 7012        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7013        assert_text_with_selections(
 7014            editor,
 7015            indoc! {r#"
 7016                use mod1::mod2::{mod3, mod4};
 7017
 7018                fn fn_1(param1: bool, param2: &str) {
 7019                    let var1 = "«ˇhello» world";
 7020                }
 7021            "#},
 7022            cx,
 7023        );
 7024    });
 7025
 7026    // Test 2: Partial selection within a word
 7027    editor.update_in(cx, |editor, window, cx| {
 7028        editor.change_selections(None, window, cx, |s| {
 7029            s.select_display_ranges([
 7030                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7031            ]);
 7032        });
 7033    });
 7034    editor.update_in(cx, |editor, window, cx| {
 7035        assert_text_with_selections(
 7036            editor,
 7037            indoc! {r#"
 7038                use mod1::mod2::{mod3, mod4};
 7039
 7040                fn fn_1(param1: bool, param2: &str) {
 7041                    let var1 = "h«elˇ»lo world";
 7042                }
 7043            "#},
 7044            cx,
 7045        );
 7046        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7047        assert_text_with_selections(
 7048            editor,
 7049            indoc! {r#"
 7050                use mod1::mod2::{mod3, mod4};
 7051
 7052                fn fn_1(param1: bool, param2: &str) {
 7053                    let var1 = "«ˇhello» world";
 7054                }
 7055            "#},
 7056            cx,
 7057        );
 7058    });
 7059
 7060    // Test 3: Complete word already selected
 7061    editor.update_in(cx, |editor, window, cx| {
 7062        editor.change_selections(None, window, cx, |s| {
 7063            s.select_display_ranges([
 7064                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7065            ]);
 7066        });
 7067    });
 7068    editor.update_in(cx, |editor, window, cx| {
 7069        assert_text_with_selections(
 7070            editor,
 7071            indoc! {r#"
 7072                use mod1::mod2::{mod3, mod4};
 7073
 7074                fn fn_1(param1: bool, param2: &str) {
 7075                    let var1 = "«helloˇ» world";
 7076                }
 7077            "#},
 7078            cx,
 7079        );
 7080        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7081        assert_text_with_selections(
 7082            editor,
 7083            indoc! {r#"
 7084                use mod1::mod2::{mod3, mod4};
 7085
 7086                fn fn_1(param1: bool, param2: &str) {
 7087                    let var1 = "«hello worldˇ»";
 7088                }
 7089            "#},
 7090            cx,
 7091        );
 7092    });
 7093
 7094    // Test 4: Selection spanning across words
 7095    editor.update_in(cx, |editor, window, cx| {
 7096        editor.change_selections(None, window, cx, |s| {
 7097            s.select_display_ranges([
 7098                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7099            ]);
 7100        });
 7101    });
 7102    editor.update_in(cx, |editor, window, cx| {
 7103        assert_text_with_selections(
 7104            editor,
 7105            indoc! {r#"
 7106                use mod1::mod2::{mod3, mod4};
 7107
 7108                fn fn_1(param1: bool, param2: &str) {
 7109                    let var1 = "hel«lo woˇ»rld";
 7110                }
 7111            "#},
 7112            cx,
 7113        );
 7114        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7115        assert_text_with_selections(
 7116            editor,
 7117            indoc! {r#"
 7118                use mod1::mod2::{mod3, mod4};
 7119
 7120                fn fn_1(param1: bool, param2: &str) {
 7121                    let var1 = "«ˇhello world»";
 7122                }
 7123            "#},
 7124            cx,
 7125        );
 7126    });
 7127
 7128    // Test 5: Expansion beyond string
 7129    editor.update_in(cx, |editor, window, cx| {
 7130        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7131        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7132        assert_text_with_selections(
 7133            editor,
 7134            indoc! {r#"
 7135                use mod1::mod2::{mod3, mod4};
 7136
 7137                fn fn_1(param1: bool, param2: &str) {
 7138                    «ˇlet var1 = "hello world";»
 7139                }
 7140            "#},
 7141            cx,
 7142        );
 7143    });
 7144}
 7145
 7146#[gpui::test]
 7147async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7148    init_test(cx, |_| {});
 7149
 7150    let base_text = r#"
 7151        impl A {
 7152            // this is an uncommitted comment
 7153
 7154            fn b() {
 7155                c();
 7156            }
 7157
 7158            // this is another uncommitted comment
 7159
 7160            fn d() {
 7161                // e
 7162                // f
 7163            }
 7164        }
 7165
 7166        fn g() {
 7167            // h
 7168        }
 7169    "#
 7170    .unindent();
 7171
 7172    let text = r#"
 7173        ˇimpl A {
 7174
 7175            fn b() {
 7176                c();
 7177            }
 7178
 7179            fn d() {
 7180                // e
 7181                // f
 7182            }
 7183        }
 7184
 7185        fn g() {
 7186            // h
 7187        }
 7188    "#
 7189    .unindent();
 7190
 7191    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7192    cx.set_state(&text);
 7193    cx.set_head_text(&base_text);
 7194    cx.update_editor(|editor, window, cx| {
 7195        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7196    });
 7197
 7198    cx.assert_state_with_diff(
 7199        "
 7200        ˇimpl A {
 7201      -     // this is an uncommitted comment
 7202
 7203            fn b() {
 7204                c();
 7205            }
 7206
 7207      -     // this is another uncommitted comment
 7208      -
 7209            fn d() {
 7210                // e
 7211                // f
 7212            }
 7213        }
 7214
 7215        fn g() {
 7216            // h
 7217        }
 7218    "
 7219        .unindent(),
 7220    );
 7221
 7222    let expected_display_text = "
 7223        impl A {
 7224            // this is an uncommitted comment
 7225
 7226            fn b() {
 7227 7228            }
 7229
 7230            // this is another uncommitted comment
 7231
 7232            fn d() {
 7233 7234            }
 7235        }
 7236
 7237        fn g() {
 7238 7239        }
 7240        "
 7241    .unindent();
 7242
 7243    cx.update_editor(|editor, window, cx| {
 7244        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7245        assert_eq!(editor.display_text(cx), expected_display_text);
 7246    });
 7247}
 7248
 7249#[gpui::test]
 7250async fn test_autoindent(cx: &mut TestAppContext) {
 7251    init_test(cx, |_| {});
 7252
 7253    let language = Arc::new(
 7254        Language::new(
 7255            LanguageConfig {
 7256                brackets: BracketPairConfig {
 7257                    pairs: vec![
 7258                        BracketPair {
 7259                            start: "{".to_string(),
 7260                            end: "}".to_string(),
 7261                            close: false,
 7262                            surround: false,
 7263                            newline: true,
 7264                        },
 7265                        BracketPair {
 7266                            start: "(".to_string(),
 7267                            end: ")".to_string(),
 7268                            close: false,
 7269                            surround: false,
 7270                            newline: true,
 7271                        },
 7272                    ],
 7273                    ..Default::default()
 7274                },
 7275                ..Default::default()
 7276            },
 7277            Some(tree_sitter_rust::LANGUAGE.into()),
 7278        )
 7279        .with_indents_query(
 7280            r#"
 7281                (_ "(" ")" @end) @indent
 7282                (_ "{" "}" @end) @indent
 7283            "#,
 7284        )
 7285        .unwrap(),
 7286    );
 7287
 7288    let text = "fn a() {}";
 7289
 7290    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7291    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7292    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7293    editor
 7294        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7295        .await;
 7296
 7297    editor.update_in(cx, |editor, window, cx| {
 7298        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7299        editor.newline(&Newline, window, cx);
 7300        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7301        assert_eq!(
 7302            editor.selections.ranges(cx),
 7303            &[
 7304                Point::new(1, 4)..Point::new(1, 4),
 7305                Point::new(3, 4)..Point::new(3, 4),
 7306                Point::new(5, 0)..Point::new(5, 0)
 7307            ]
 7308        );
 7309    });
 7310}
 7311
 7312#[gpui::test]
 7313async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7314    init_test(cx, |_| {});
 7315
 7316    {
 7317        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7318        cx.set_state(indoc! {"
 7319            impl A {
 7320
 7321                fn b() {}
 7322
 7323            «fn c() {
 7324
 7325            }ˇ»
 7326            }
 7327        "});
 7328
 7329        cx.update_editor(|editor, window, cx| {
 7330            editor.autoindent(&Default::default(), window, cx);
 7331        });
 7332
 7333        cx.assert_editor_state(indoc! {"
 7334            impl A {
 7335
 7336                fn b() {}
 7337
 7338                «fn c() {
 7339
 7340                }ˇ»
 7341            }
 7342        "});
 7343    }
 7344
 7345    {
 7346        let mut cx = EditorTestContext::new_multibuffer(
 7347            cx,
 7348            [indoc! { "
 7349                impl A {
 7350                «
 7351                // a
 7352                fn b(){}
 7353                »
 7354                «
 7355                    }
 7356                    fn c(){}
 7357                »
 7358            "}],
 7359        );
 7360
 7361        let buffer = cx.update_editor(|editor, _, cx| {
 7362            let buffer = editor.buffer().update(cx, |buffer, _| {
 7363                buffer.all_buffers().iter().next().unwrap().clone()
 7364            });
 7365            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7366            buffer
 7367        });
 7368
 7369        cx.run_until_parked();
 7370        cx.update_editor(|editor, window, cx| {
 7371            editor.select_all(&Default::default(), window, cx);
 7372            editor.autoindent(&Default::default(), window, cx)
 7373        });
 7374        cx.run_until_parked();
 7375
 7376        cx.update(|_, cx| {
 7377            assert_eq!(
 7378                buffer.read(cx).text(),
 7379                indoc! { "
 7380                    impl A {
 7381
 7382                        // a
 7383                        fn b(){}
 7384
 7385
 7386                    }
 7387                    fn c(){}
 7388
 7389                " }
 7390            )
 7391        });
 7392    }
 7393}
 7394
 7395#[gpui::test]
 7396async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7397    init_test(cx, |_| {});
 7398
 7399    let mut cx = EditorTestContext::new(cx).await;
 7400
 7401    let language = Arc::new(Language::new(
 7402        LanguageConfig {
 7403            brackets: BracketPairConfig {
 7404                pairs: vec![
 7405                    BracketPair {
 7406                        start: "{".to_string(),
 7407                        end: "}".to_string(),
 7408                        close: true,
 7409                        surround: true,
 7410                        newline: true,
 7411                    },
 7412                    BracketPair {
 7413                        start: "(".to_string(),
 7414                        end: ")".to_string(),
 7415                        close: true,
 7416                        surround: true,
 7417                        newline: true,
 7418                    },
 7419                    BracketPair {
 7420                        start: "/*".to_string(),
 7421                        end: " */".to_string(),
 7422                        close: true,
 7423                        surround: true,
 7424                        newline: true,
 7425                    },
 7426                    BracketPair {
 7427                        start: "[".to_string(),
 7428                        end: "]".to_string(),
 7429                        close: false,
 7430                        surround: false,
 7431                        newline: true,
 7432                    },
 7433                    BracketPair {
 7434                        start: "\"".to_string(),
 7435                        end: "\"".to_string(),
 7436                        close: true,
 7437                        surround: true,
 7438                        newline: false,
 7439                    },
 7440                    BracketPair {
 7441                        start: "<".to_string(),
 7442                        end: ">".to_string(),
 7443                        close: false,
 7444                        surround: true,
 7445                        newline: true,
 7446                    },
 7447                ],
 7448                ..Default::default()
 7449            },
 7450            autoclose_before: "})]".to_string(),
 7451            ..Default::default()
 7452        },
 7453        Some(tree_sitter_rust::LANGUAGE.into()),
 7454    ));
 7455
 7456    cx.language_registry().add(language.clone());
 7457    cx.update_buffer(|buffer, cx| {
 7458        buffer.set_language(Some(language), cx);
 7459    });
 7460
 7461    cx.set_state(
 7462        &r#"
 7463            🏀ˇ
 7464            εˇ
 7465            ❤️ˇ
 7466        "#
 7467        .unindent(),
 7468    );
 7469
 7470    // autoclose multiple nested brackets at multiple cursors
 7471    cx.update_editor(|editor, window, cx| {
 7472        editor.handle_input("{", window, cx);
 7473        editor.handle_input("{", window, cx);
 7474        editor.handle_input("{", window, cx);
 7475    });
 7476    cx.assert_editor_state(
 7477        &"
 7478            🏀{{{ˇ}}}
 7479            ε{{{ˇ}}}
 7480            ❤️{{{ˇ}}}
 7481        "
 7482        .unindent(),
 7483    );
 7484
 7485    // insert a different closing bracket
 7486    cx.update_editor(|editor, window, cx| {
 7487        editor.handle_input(")", window, cx);
 7488    });
 7489    cx.assert_editor_state(
 7490        &"
 7491            🏀{{{)ˇ}}}
 7492            ε{{{)ˇ}}}
 7493            ❤️{{{)ˇ}}}
 7494        "
 7495        .unindent(),
 7496    );
 7497
 7498    // skip over the auto-closed brackets when typing a closing bracket
 7499    cx.update_editor(|editor, window, cx| {
 7500        editor.move_right(&MoveRight, window, cx);
 7501        editor.handle_input("}", window, cx);
 7502        editor.handle_input("}", window, cx);
 7503        editor.handle_input("}", window, cx);
 7504    });
 7505    cx.assert_editor_state(
 7506        &"
 7507            🏀{{{)}}}}ˇ
 7508            ε{{{)}}}}ˇ
 7509            ❤️{{{)}}}}ˇ
 7510        "
 7511        .unindent(),
 7512    );
 7513
 7514    // autoclose multi-character pairs
 7515    cx.set_state(
 7516        &"
 7517            ˇ
 7518            ˇ
 7519        "
 7520        .unindent(),
 7521    );
 7522    cx.update_editor(|editor, window, cx| {
 7523        editor.handle_input("/", window, cx);
 7524        editor.handle_input("*", window, cx);
 7525    });
 7526    cx.assert_editor_state(
 7527        &"
 7528            /*ˇ */
 7529            /*ˇ */
 7530        "
 7531        .unindent(),
 7532    );
 7533
 7534    // one cursor autocloses a multi-character pair, one cursor
 7535    // does not autoclose.
 7536    cx.set_state(
 7537        &"
 7538 7539            ˇ
 7540        "
 7541        .unindent(),
 7542    );
 7543    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7544    cx.assert_editor_state(
 7545        &"
 7546            /*ˇ */
 7547 7548        "
 7549        .unindent(),
 7550    );
 7551
 7552    // Don't autoclose if the next character isn't whitespace and isn't
 7553    // listed in the language's "autoclose_before" section.
 7554    cx.set_state("ˇa b");
 7555    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7556    cx.assert_editor_state("{ˇa b");
 7557
 7558    // Don't autoclose if `close` is false for the bracket pair
 7559    cx.set_state("ˇ");
 7560    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7561    cx.assert_editor_state("");
 7562
 7563    // Surround with brackets if text is selected
 7564    cx.set_state("«aˇ» b");
 7565    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7566    cx.assert_editor_state("{«aˇ»} b");
 7567
 7568    // Autoclose when not immediately after a word character
 7569    cx.set_state("a ˇ");
 7570    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7571    cx.assert_editor_state("a \"ˇ\"");
 7572
 7573    // Autoclose pair where the start and end characters are the same
 7574    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7575    cx.assert_editor_state("a \"\"ˇ");
 7576
 7577    // Don't autoclose when immediately after a word character
 7578    cx.set_state("");
 7579    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7580    cx.assert_editor_state("a\"ˇ");
 7581
 7582    // Do autoclose when after a non-word character
 7583    cx.set_state("");
 7584    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7585    cx.assert_editor_state("{\"ˇ\"");
 7586
 7587    // Non identical pairs autoclose regardless of preceding character
 7588    cx.set_state("");
 7589    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7590    cx.assert_editor_state("a{ˇ}");
 7591
 7592    // Don't autoclose pair if autoclose is disabled
 7593    cx.set_state("ˇ");
 7594    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7595    cx.assert_editor_state("");
 7596
 7597    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7598    cx.set_state("«aˇ» b");
 7599    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7600    cx.assert_editor_state("<«aˇ»> b");
 7601}
 7602
 7603#[gpui::test]
 7604async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7605    init_test(cx, |settings| {
 7606        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7607    });
 7608
 7609    let mut cx = EditorTestContext::new(cx).await;
 7610
 7611    let language = Arc::new(Language::new(
 7612        LanguageConfig {
 7613            brackets: BracketPairConfig {
 7614                pairs: vec![
 7615                    BracketPair {
 7616                        start: "{".to_string(),
 7617                        end: "}".to_string(),
 7618                        close: true,
 7619                        surround: true,
 7620                        newline: true,
 7621                    },
 7622                    BracketPair {
 7623                        start: "(".to_string(),
 7624                        end: ")".to_string(),
 7625                        close: true,
 7626                        surround: true,
 7627                        newline: true,
 7628                    },
 7629                    BracketPair {
 7630                        start: "[".to_string(),
 7631                        end: "]".to_string(),
 7632                        close: false,
 7633                        surround: false,
 7634                        newline: true,
 7635                    },
 7636                ],
 7637                ..Default::default()
 7638            },
 7639            autoclose_before: "})]".to_string(),
 7640            ..Default::default()
 7641        },
 7642        Some(tree_sitter_rust::LANGUAGE.into()),
 7643    ));
 7644
 7645    cx.language_registry().add(language.clone());
 7646    cx.update_buffer(|buffer, cx| {
 7647        buffer.set_language(Some(language), cx);
 7648    });
 7649
 7650    cx.set_state(
 7651        &"
 7652            ˇ
 7653            ˇ
 7654            ˇ
 7655        "
 7656        .unindent(),
 7657    );
 7658
 7659    // ensure only matching closing brackets are skipped over
 7660    cx.update_editor(|editor, window, cx| {
 7661        editor.handle_input("}", window, cx);
 7662        editor.move_left(&MoveLeft, window, cx);
 7663        editor.handle_input(")", window, cx);
 7664        editor.move_left(&MoveLeft, window, cx);
 7665    });
 7666    cx.assert_editor_state(
 7667        &"
 7668            ˇ)}
 7669            ˇ)}
 7670            ˇ)}
 7671        "
 7672        .unindent(),
 7673    );
 7674
 7675    // skip-over closing brackets at multiple cursors
 7676    cx.update_editor(|editor, window, cx| {
 7677        editor.handle_input(")", window, cx);
 7678        editor.handle_input("}", window, cx);
 7679    });
 7680    cx.assert_editor_state(
 7681        &"
 7682            )}ˇ
 7683            )}ˇ
 7684            )}ˇ
 7685        "
 7686        .unindent(),
 7687    );
 7688
 7689    // ignore non-close brackets
 7690    cx.update_editor(|editor, window, cx| {
 7691        editor.handle_input("]", window, cx);
 7692        editor.move_left(&MoveLeft, window, cx);
 7693        editor.handle_input("]", window, cx);
 7694    });
 7695    cx.assert_editor_state(
 7696        &"
 7697            )}]ˇ]
 7698            )}]ˇ]
 7699            )}]ˇ]
 7700        "
 7701        .unindent(),
 7702    );
 7703}
 7704
 7705#[gpui::test]
 7706async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 7707    init_test(cx, |_| {});
 7708
 7709    let mut cx = EditorTestContext::new(cx).await;
 7710
 7711    let html_language = Arc::new(
 7712        Language::new(
 7713            LanguageConfig {
 7714                name: "HTML".into(),
 7715                brackets: BracketPairConfig {
 7716                    pairs: vec![
 7717                        BracketPair {
 7718                            start: "<".into(),
 7719                            end: ">".into(),
 7720                            close: true,
 7721                            ..Default::default()
 7722                        },
 7723                        BracketPair {
 7724                            start: "{".into(),
 7725                            end: "}".into(),
 7726                            close: true,
 7727                            ..Default::default()
 7728                        },
 7729                        BracketPair {
 7730                            start: "(".into(),
 7731                            end: ")".into(),
 7732                            close: true,
 7733                            ..Default::default()
 7734                        },
 7735                    ],
 7736                    ..Default::default()
 7737                },
 7738                autoclose_before: "})]>".into(),
 7739                ..Default::default()
 7740            },
 7741            Some(tree_sitter_html::LANGUAGE.into()),
 7742        )
 7743        .with_injection_query(
 7744            r#"
 7745            (script_element
 7746                (raw_text) @injection.content
 7747                (#set! injection.language "javascript"))
 7748            "#,
 7749        )
 7750        .unwrap(),
 7751    );
 7752
 7753    let javascript_language = Arc::new(Language::new(
 7754        LanguageConfig {
 7755            name: "JavaScript".into(),
 7756            brackets: BracketPairConfig {
 7757                pairs: vec![
 7758                    BracketPair {
 7759                        start: "/*".into(),
 7760                        end: " */".into(),
 7761                        close: true,
 7762                        ..Default::default()
 7763                    },
 7764                    BracketPair {
 7765                        start: "{".into(),
 7766                        end: "}".into(),
 7767                        close: true,
 7768                        ..Default::default()
 7769                    },
 7770                    BracketPair {
 7771                        start: "(".into(),
 7772                        end: ")".into(),
 7773                        close: true,
 7774                        ..Default::default()
 7775                    },
 7776                ],
 7777                ..Default::default()
 7778            },
 7779            autoclose_before: "})]>".into(),
 7780            ..Default::default()
 7781        },
 7782        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 7783    ));
 7784
 7785    cx.language_registry().add(html_language.clone());
 7786    cx.language_registry().add(javascript_language.clone());
 7787
 7788    cx.update_buffer(|buffer, cx| {
 7789        buffer.set_language(Some(html_language), cx);
 7790    });
 7791
 7792    cx.set_state(
 7793        &r#"
 7794            <body>ˇ
 7795                <script>
 7796                    var x = 1;ˇ
 7797                </script>
 7798            </body>ˇ
 7799        "#
 7800        .unindent(),
 7801    );
 7802
 7803    // Precondition: different languages are active at different locations.
 7804    cx.update_editor(|editor, window, cx| {
 7805        let snapshot = editor.snapshot(window, cx);
 7806        let cursors = editor.selections.ranges::<usize>(cx);
 7807        let languages = cursors
 7808            .iter()
 7809            .map(|c| snapshot.language_at(c.start).unwrap().name())
 7810            .collect::<Vec<_>>();
 7811        assert_eq!(
 7812            languages,
 7813            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 7814        );
 7815    });
 7816
 7817    // Angle brackets autoclose in HTML, but not JavaScript.
 7818    cx.update_editor(|editor, window, cx| {
 7819        editor.handle_input("<", window, cx);
 7820        editor.handle_input("a", window, cx);
 7821    });
 7822    cx.assert_editor_state(
 7823        &r#"
 7824            <body><aˇ>
 7825                <script>
 7826                    var x = 1;<aˇ
 7827                </script>
 7828            </body><aˇ>
 7829        "#
 7830        .unindent(),
 7831    );
 7832
 7833    // Curly braces and parens autoclose in both HTML and JavaScript.
 7834    cx.update_editor(|editor, window, cx| {
 7835        editor.handle_input(" b=", window, cx);
 7836        editor.handle_input("{", window, cx);
 7837        editor.handle_input("c", window, cx);
 7838        editor.handle_input("(", window, cx);
 7839    });
 7840    cx.assert_editor_state(
 7841        &r#"
 7842            <body><a b={c(ˇ)}>
 7843                <script>
 7844                    var x = 1;<a b={c(ˇ)}
 7845                </script>
 7846            </body><a b={c(ˇ)}>
 7847        "#
 7848        .unindent(),
 7849    );
 7850
 7851    // Brackets that were already autoclosed are skipped.
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.handle_input(")", window, cx);
 7854        editor.handle_input("d", window, cx);
 7855        editor.handle_input("}", window, cx);
 7856    });
 7857    cx.assert_editor_state(
 7858        &r#"
 7859            <body><a b={c()d}ˇ>
 7860                <script>
 7861                    var x = 1;<a b={c()d}ˇ
 7862                </script>
 7863            </body><a b={c()d}ˇ>
 7864        "#
 7865        .unindent(),
 7866    );
 7867    cx.update_editor(|editor, window, cx| {
 7868        editor.handle_input(">", window, cx);
 7869    });
 7870    cx.assert_editor_state(
 7871        &r#"
 7872            <body><a b={c()d}>ˇ
 7873                <script>
 7874                    var x = 1;<a b={c()d}>ˇ
 7875                </script>
 7876            </body><a b={c()d}>ˇ
 7877        "#
 7878        .unindent(),
 7879    );
 7880
 7881    // Reset
 7882    cx.set_state(
 7883        &r#"
 7884            <body>ˇ
 7885                <script>
 7886                    var x = 1;ˇ
 7887                </script>
 7888            </body>ˇ
 7889        "#
 7890        .unindent(),
 7891    );
 7892
 7893    cx.update_editor(|editor, window, cx| {
 7894        editor.handle_input("<", window, cx);
 7895    });
 7896    cx.assert_editor_state(
 7897        &r#"
 7898            <body><ˇ>
 7899                <script>
 7900                    var x = 1;<ˇ
 7901                </script>
 7902            </body><ˇ>
 7903        "#
 7904        .unindent(),
 7905    );
 7906
 7907    // When backspacing, the closing angle brackets are removed.
 7908    cx.update_editor(|editor, window, cx| {
 7909        editor.backspace(&Backspace, window, cx);
 7910    });
 7911    cx.assert_editor_state(
 7912        &r#"
 7913            <body>ˇ
 7914                <script>
 7915                    var x = 1;ˇ
 7916                </script>
 7917            </body>ˇ
 7918        "#
 7919        .unindent(),
 7920    );
 7921
 7922    // Block comments autoclose in JavaScript, but not HTML.
 7923    cx.update_editor(|editor, window, cx| {
 7924        editor.handle_input("/", window, cx);
 7925        editor.handle_input("*", window, cx);
 7926    });
 7927    cx.assert_editor_state(
 7928        &r#"
 7929            <body>/*ˇ
 7930                <script>
 7931                    var x = 1;/*ˇ */
 7932                </script>
 7933            </body>/*ˇ
 7934        "#
 7935        .unindent(),
 7936    );
 7937}
 7938
 7939#[gpui::test]
 7940async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 7941    init_test(cx, |_| {});
 7942
 7943    let mut cx = EditorTestContext::new(cx).await;
 7944
 7945    let rust_language = Arc::new(
 7946        Language::new(
 7947            LanguageConfig {
 7948                name: "Rust".into(),
 7949                brackets: serde_json::from_value(json!([
 7950                    { "start": "{", "end": "}", "close": true, "newline": true },
 7951                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 7952                ]))
 7953                .unwrap(),
 7954                autoclose_before: "})]>".into(),
 7955                ..Default::default()
 7956            },
 7957            Some(tree_sitter_rust::LANGUAGE.into()),
 7958        )
 7959        .with_override_query("(string_literal) @string")
 7960        .unwrap(),
 7961    );
 7962
 7963    cx.language_registry().add(rust_language.clone());
 7964    cx.update_buffer(|buffer, cx| {
 7965        buffer.set_language(Some(rust_language), cx);
 7966    });
 7967
 7968    cx.set_state(
 7969        &r#"
 7970            let x = ˇ
 7971        "#
 7972        .unindent(),
 7973    );
 7974
 7975    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7976    cx.update_editor(|editor, window, cx| {
 7977        editor.handle_input("\"", window, cx);
 7978    });
 7979    cx.assert_editor_state(
 7980        &r#"
 7981            let x = "ˇ"
 7982        "#
 7983        .unindent(),
 7984    );
 7985
 7986    // Inserting another quotation mark. The cursor moves across the existing
 7987    // automatically-inserted quotation mark.
 7988    cx.update_editor(|editor, window, cx| {
 7989        editor.handle_input("\"", window, cx);
 7990    });
 7991    cx.assert_editor_state(
 7992        &r#"
 7993            let x = ""ˇ
 7994        "#
 7995        .unindent(),
 7996    );
 7997
 7998    // Reset
 7999    cx.set_state(
 8000        &r#"
 8001            let x = ˇ
 8002        "#
 8003        .unindent(),
 8004    );
 8005
 8006    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8007    cx.update_editor(|editor, window, cx| {
 8008        editor.handle_input("\"", window, cx);
 8009        editor.handle_input(" ", window, cx);
 8010        editor.move_left(&Default::default(), window, cx);
 8011        editor.handle_input("\\", window, cx);
 8012        editor.handle_input("\"", window, cx);
 8013    });
 8014    cx.assert_editor_state(
 8015        &r#"
 8016            let x = "\"ˇ "
 8017        "#
 8018        .unindent(),
 8019    );
 8020
 8021    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8022    // mark. Nothing is inserted.
 8023    cx.update_editor(|editor, window, cx| {
 8024        editor.move_right(&Default::default(), window, cx);
 8025        editor.handle_input("\"", window, cx);
 8026    });
 8027    cx.assert_editor_state(
 8028        &r#"
 8029            let x = "\" "ˇ
 8030        "#
 8031        .unindent(),
 8032    );
 8033}
 8034
 8035#[gpui::test]
 8036async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8037    init_test(cx, |_| {});
 8038
 8039    let language = Arc::new(Language::new(
 8040        LanguageConfig {
 8041            brackets: BracketPairConfig {
 8042                pairs: vec![
 8043                    BracketPair {
 8044                        start: "{".to_string(),
 8045                        end: "}".to_string(),
 8046                        close: true,
 8047                        surround: true,
 8048                        newline: true,
 8049                    },
 8050                    BracketPair {
 8051                        start: "/* ".to_string(),
 8052                        end: "*/".to_string(),
 8053                        close: true,
 8054                        surround: true,
 8055                        ..Default::default()
 8056                    },
 8057                ],
 8058                ..Default::default()
 8059            },
 8060            ..Default::default()
 8061        },
 8062        Some(tree_sitter_rust::LANGUAGE.into()),
 8063    ));
 8064
 8065    let text = r#"
 8066        a
 8067        b
 8068        c
 8069    "#
 8070    .unindent();
 8071
 8072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8075    editor
 8076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8077        .await;
 8078
 8079    editor.update_in(cx, |editor, window, cx| {
 8080        editor.change_selections(None, window, cx, |s| {
 8081            s.select_display_ranges([
 8082                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8083                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8084                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8085            ])
 8086        });
 8087
 8088        editor.handle_input("{", window, cx);
 8089        editor.handle_input("{", window, cx);
 8090        editor.handle_input("{", window, cx);
 8091        assert_eq!(
 8092            editor.text(cx),
 8093            "
 8094                {{{a}}}
 8095                {{{b}}}
 8096                {{{c}}}
 8097            "
 8098            .unindent()
 8099        );
 8100        assert_eq!(
 8101            editor.selections.display_ranges(cx),
 8102            [
 8103                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8104                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8105                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8106            ]
 8107        );
 8108
 8109        editor.undo(&Undo, window, cx);
 8110        editor.undo(&Undo, window, cx);
 8111        editor.undo(&Undo, window, cx);
 8112        assert_eq!(
 8113            editor.text(cx),
 8114            "
 8115                a
 8116                b
 8117                c
 8118            "
 8119            .unindent()
 8120        );
 8121        assert_eq!(
 8122            editor.selections.display_ranges(cx),
 8123            [
 8124                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8125                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8126                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8127            ]
 8128        );
 8129
 8130        // Ensure inserting the first character of a multi-byte bracket pair
 8131        // doesn't surround the selections with the bracket.
 8132        editor.handle_input("/", window, cx);
 8133        assert_eq!(
 8134            editor.text(cx),
 8135            "
 8136                /
 8137                /
 8138                /
 8139            "
 8140            .unindent()
 8141        );
 8142        assert_eq!(
 8143            editor.selections.display_ranges(cx),
 8144            [
 8145                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8146                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8147                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8148            ]
 8149        );
 8150
 8151        editor.undo(&Undo, window, cx);
 8152        assert_eq!(
 8153            editor.text(cx),
 8154            "
 8155                a
 8156                b
 8157                c
 8158            "
 8159            .unindent()
 8160        );
 8161        assert_eq!(
 8162            editor.selections.display_ranges(cx),
 8163            [
 8164                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8165                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8166                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8167            ]
 8168        );
 8169
 8170        // Ensure inserting the last character of a multi-byte bracket pair
 8171        // doesn't surround the selections with the bracket.
 8172        editor.handle_input("*", window, cx);
 8173        assert_eq!(
 8174            editor.text(cx),
 8175            "
 8176                *
 8177                *
 8178                *
 8179            "
 8180            .unindent()
 8181        );
 8182        assert_eq!(
 8183            editor.selections.display_ranges(cx),
 8184            [
 8185                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8186                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8187                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8188            ]
 8189        );
 8190    });
 8191}
 8192
 8193#[gpui::test]
 8194async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8195    init_test(cx, |_| {});
 8196
 8197    let language = Arc::new(Language::new(
 8198        LanguageConfig {
 8199            brackets: BracketPairConfig {
 8200                pairs: vec![BracketPair {
 8201                    start: "{".to_string(),
 8202                    end: "}".to_string(),
 8203                    close: true,
 8204                    surround: true,
 8205                    newline: true,
 8206                }],
 8207                ..Default::default()
 8208            },
 8209            autoclose_before: "}".to_string(),
 8210            ..Default::default()
 8211        },
 8212        Some(tree_sitter_rust::LANGUAGE.into()),
 8213    ));
 8214
 8215    let text = r#"
 8216        a
 8217        b
 8218        c
 8219    "#
 8220    .unindent();
 8221
 8222    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8223    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8224    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8225    editor
 8226        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8227        .await;
 8228
 8229    editor.update_in(cx, |editor, window, cx| {
 8230        editor.change_selections(None, window, cx, |s| {
 8231            s.select_ranges([
 8232                Point::new(0, 1)..Point::new(0, 1),
 8233                Point::new(1, 1)..Point::new(1, 1),
 8234                Point::new(2, 1)..Point::new(2, 1),
 8235            ])
 8236        });
 8237
 8238        editor.handle_input("{", window, cx);
 8239        editor.handle_input("{", window, cx);
 8240        editor.handle_input("_", window, cx);
 8241        assert_eq!(
 8242            editor.text(cx),
 8243            "
 8244                a{{_}}
 8245                b{{_}}
 8246                c{{_}}
 8247            "
 8248            .unindent()
 8249        );
 8250        assert_eq!(
 8251            editor.selections.ranges::<Point>(cx),
 8252            [
 8253                Point::new(0, 4)..Point::new(0, 4),
 8254                Point::new(1, 4)..Point::new(1, 4),
 8255                Point::new(2, 4)..Point::new(2, 4)
 8256            ]
 8257        );
 8258
 8259        editor.backspace(&Default::default(), window, cx);
 8260        editor.backspace(&Default::default(), window, cx);
 8261        assert_eq!(
 8262            editor.text(cx),
 8263            "
 8264                a{}
 8265                b{}
 8266                c{}
 8267            "
 8268            .unindent()
 8269        );
 8270        assert_eq!(
 8271            editor.selections.ranges::<Point>(cx),
 8272            [
 8273                Point::new(0, 2)..Point::new(0, 2),
 8274                Point::new(1, 2)..Point::new(1, 2),
 8275                Point::new(2, 2)..Point::new(2, 2)
 8276            ]
 8277        );
 8278
 8279        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8280        assert_eq!(
 8281            editor.text(cx),
 8282            "
 8283                a
 8284                b
 8285                c
 8286            "
 8287            .unindent()
 8288        );
 8289        assert_eq!(
 8290            editor.selections.ranges::<Point>(cx),
 8291            [
 8292                Point::new(0, 1)..Point::new(0, 1),
 8293                Point::new(1, 1)..Point::new(1, 1),
 8294                Point::new(2, 1)..Point::new(2, 1)
 8295            ]
 8296        );
 8297    });
 8298}
 8299
 8300#[gpui::test]
 8301async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8302    init_test(cx, |settings| {
 8303        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8304    });
 8305
 8306    let mut cx = EditorTestContext::new(cx).await;
 8307
 8308    let language = Arc::new(Language::new(
 8309        LanguageConfig {
 8310            brackets: BracketPairConfig {
 8311                pairs: vec![
 8312                    BracketPair {
 8313                        start: "{".to_string(),
 8314                        end: "}".to_string(),
 8315                        close: true,
 8316                        surround: true,
 8317                        newline: true,
 8318                    },
 8319                    BracketPair {
 8320                        start: "(".to_string(),
 8321                        end: ")".to_string(),
 8322                        close: true,
 8323                        surround: true,
 8324                        newline: true,
 8325                    },
 8326                    BracketPair {
 8327                        start: "[".to_string(),
 8328                        end: "]".to_string(),
 8329                        close: false,
 8330                        surround: true,
 8331                        newline: true,
 8332                    },
 8333                ],
 8334                ..Default::default()
 8335            },
 8336            autoclose_before: "})]".to_string(),
 8337            ..Default::default()
 8338        },
 8339        Some(tree_sitter_rust::LANGUAGE.into()),
 8340    ));
 8341
 8342    cx.language_registry().add(language.clone());
 8343    cx.update_buffer(|buffer, cx| {
 8344        buffer.set_language(Some(language), cx);
 8345    });
 8346
 8347    cx.set_state(
 8348        &"
 8349            {(ˇ)}
 8350            [[ˇ]]
 8351            {(ˇ)}
 8352        "
 8353        .unindent(),
 8354    );
 8355
 8356    cx.update_editor(|editor, window, cx| {
 8357        editor.backspace(&Default::default(), window, cx);
 8358        editor.backspace(&Default::default(), window, cx);
 8359    });
 8360
 8361    cx.assert_editor_state(
 8362        &"
 8363            ˇ
 8364            ˇ]]
 8365            ˇ
 8366        "
 8367        .unindent(),
 8368    );
 8369
 8370    cx.update_editor(|editor, window, cx| {
 8371        editor.handle_input("{", window, cx);
 8372        editor.handle_input("{", window, cx);
 8373        editor.move_right(&MoveRight, window, cx);
 8374        editor.move_right(&MoveRight, window, cx);
 8375        editor.move_left(&MoveLeft, window, cx);
 8376        editor.move_left(&MoveLeft, window, cx);
 8377        editor.backspace(&Default::default(), window, cx);
 8378    });
 8379
 8380    cx.assert_editor_state(
 8381        &"
 8382            {ˇ}
 8383            {ˇ}]]
 8384            {ˇ}
 8385        "
 8386        .unindent(),
 8387    );
 8388
 8389    cx.update_editor(|editor, window, cx| {
 8390        editor.backspace(&Default::default(), window, cx);
 8391    });
 8392
 8393    cx.assert_editor_state(
 8394        &"
 8395            ˇ
 8396            ˇ]]
 8397            ˇ
 8398        "
 8399        .unindent(),
 8400    );
 8401}
 8402
 8403#[gpui::test]
 8404async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8405    init_test(cx, |_| {});
 8406
 8407    let language = Arc::new(Language::new(
 8408        LanguageConfig::default(),
 8409        Some(tree_sitter_rust::LANGUAGE.into()),
 8410    ));
 8411
 8412    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8413    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8414    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8415    editor
 8416        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8417        .await;
 8418
 8419    editor.update_in(cx, |editor, window, cx| {
 8420        editor.set_auto_replace_emoji_shortcode(true);
 8421
 8422        editor.handle_input("Hello ", window, cx);
 8423        editor.handle_input(":wave", window, cx);
 8424        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8425
 8426        editor.handle_input(":", window, cx);
 8427        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8428
 8429        editor.handle_input(" :smile", window, cx);
 8430        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8431
 8432        editor.handle_input(":", window, cx);
 8433        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8434
 8435        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8436        editor.handle_input(":wave", window, cx);
 8437        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8438
 8439        editor.handle_input(":", window, cx);
 8440        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8441
 8442        editor.handle_input(":1", window, cx);
 8443        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8444
 8445        editor.handle_input(":", window, cx);
 8446        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8447
 8448        // Ensure shortcode does not get replaced when it is part of a word
 8449        editor.handle_input(" Test:wave", window, cx);
 8450        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8451
 8452        editor.handle_input(":", window, cx);
 8453        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8454
 8455        editor.set_auto_replace_emoji_shortcode(false);
 8456
 8457        // Ensure shortcode does not get replaced when auto replace is off
 8458        editor.handle_input(" :wave", window, cx);
 8459        assert_eq!(
 8460            editor.text(cx),
 8461            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8462        );
 8463
 8464        editor.handle_input(":", window, cx);
 8465        assert_eq!(
 8466            editor.text(cx),
 8467            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8468        );
 8469    });
 8470}
 8471
 8472#[gpui::test]
 8473async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8474    init_test(cx, |_| {});
 8475
 8476    let (text, insertion_ranges) = marked_text_ranges(
 8477        indoc! {"
 8478            ˇ
 8479        "},
 8480        false,
 8481    );
 8482
 8483    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8484    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8485
 8486    _ = editor.update_in(cx, |editor, window, cx| {
 8487        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8488
 8489        editor
 8490            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8491            .unwrap();
 8492
 8493        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8494            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8495            assert_eq!(editor.text(cx), expected_text);
 8496            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8497        }
 8498
 8499        assert(
 8500            editor,
 8501            cx,
 8502            indoc! {"
 8503            type «» =•
 8504            "},
 8505        );
 8506
 8507        assert!(editor.context_menu_visible(), "There should be a matches");
 8508    });
 8509}
 8510
 8511#[gpui::test]
 8512async fn test_snippets(cx: &mut TestAppContext) {
 8513    init_test(cx, |_| {});
 8514
 8515    let (text, insertion_ranges) = marked_text_ranges(
 8516        indoc! {"
 8517            a.ˇ b
 8518            a.ˇ b
 8519            a.ˇ b
 8520        "},
 8521        false,
 8522    );
 8523
 8524    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8525    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8526
 8527    editor.update_in(cx, |editor, window, cx| {
 8528        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8529
 8530        editor
 8531            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8532            .unwrap();
 8533
 8534        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8535            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8536            assert_eq!(editor.text(cx), expected_text);
 8537            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8538        }
 8539
 8540        assert(
 8541            editor,
 8542            cx,
 8543            indoc! {"
 8544                a.f(«one», two, «three») b
 8545                a.f(«one», two, «three») b
 8546                a.f(«one», two, «three») b
 8547            "},
 8548        );
 8549
 8550        // Can't move earlier than the first tab stop
 8551        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 8552        assert(
 8553            editor,
 8554            cx,
 8555            indoc! {"
 8556                a.f(«one», two, «three») b
 8557                a.f(«one», two, «three») b
 8558                a.f(«one», two, «three») b
 8559            "},
 8560        );
 8561
 8562        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8563        assert(
 8564            editor,
 8565            cx,
 8566            indoc! {"
 8567                a.f(one, «two», three) b
 8568                a.f(one, «two», three) b
 8569                a.f(one, «two», three) b
 8570            "},
 8571        );
 8572
 8573        editor.move_to_prev_snippet_tabstop(window, cx);
 8574        assert(
 8575            editor,
 8576            cx,
 8577            indoc! {"
 8578                a.f(«one», two, «three») b
 8579                a.f(«one», two, «three») b
 8580                a.f(«one», two, «three») b
 8581            "},
 8582        );
 8583
 8584        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8585        assert(
 8586            editor,
 8587            cx,
 8588            indoc! {"
 8589                a.f(one, «two», three) b
 8590                a.f(one, «two», three) b
 8591                a.f(one, «two», three) b
 8592            "},
 8593        );
 8594        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 8595        assert(
 8596            editor,
 8597            cx,
 8598            indoc! {"
 8599                a.f(one, two, three)ˇ b
 8600                a.f(one, two, three)ˇ b
 8601                a.f(one, two, three)ˇ b
 8602            "},
 8603        );
 8604
 8605        // As soon as the last tab stop is reached, snippet state is gone
 8606        editor.move_to_prev_snippet_tabstop(window, cx);
 8607        assert(
 8608            editor,
 8609            cx,
 8610            indoc! {"
 8611                a.f(one, two, three)ˇ b
 8612                a.f(one, two, three)ˇ b
 8613                a.f(one, two, three)ˇ b
 8614            "},
 8615        );
 8616    });
 8617}
 8618
 8619#[gpui::test]
 8620async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8621    init_test(cx, |_| {});
 8622
 8623    let fs = FakeFs::new(cx.executor());
 8624    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8625
 8626    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8627
 8628    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8629    language_registry.add(rust_lang());
 8630    let mut fake_servers = language_registry.register_fake_lsp(
 8631        "Rust",
 8632        FakeLspAdapter {
 8633            capabilities: lsp::ServerCapabilities {
 8634                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8635                ..Default::default()
 8636            },
 8637            ..Default::default()
 8638        },
 8639    );
 8640
 8641    let buffer = project
 8642        .update(cx, |project, cx| {
 8643            project.open_local_buffer(path!("/file.rs"), cx)
 8644        })
 8645        .await
 8646        .unwrap();
 8647
 8648    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8649    let (editor, cx) = cx.add_window_view(|window, cx| {
 8650        build_editor_with_project(project.clone(), buffer, window, cx)
 8651    });
 8652    editor.update_in(cx, |editor, window, cx| {
 8653        editor.set_text("one\ntwo\nthree\n", window, cx)
 8654    });
 8655    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8656
 8657    cx.executor().start_waiting();
 8658    let fake_server = fake_servers.next().await.unwrap();
 8659
 8660    {
 8661        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8662            move |params, _| async move {
 8663                assert_eq!(
 8664                    params.text_document.uri,
 8665                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8666                );
 8667                assert_eq!(params.options.tab_size, 4);
 8668                Ok(Some(vec![lsp::TextEdit::new(
 8669                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8670                    ", ".to_string(),
 8671                )]))
 8672            },
 8673        );
 8674        let save = editor
 8675            .update_in(cx, |editor, window, cx| {
 8676                editor.save(true, project.clone(), window, cx)
 8677            })
 8678            .unwrap();
 8679        cx.executor().start_waiting();
 8680        save.await;
 8681
 8682        assert_eq!(
 8683            editor.update(cx, |editor, cx| editor.text(cx)),
 8684            "one, two\nthree\n"
 8685        );
 8686        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8687    }
 8688
 8689    {
 8690        editor.update_in(cx, |editor, window, cx| {
 8691            editor.set_text("one\ntwo\nthree\n", window, cx)
 8692        });
 8693        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8694
 8695        // Ensure we can still save even if formatting hangs.
 8696        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 8697            move |params, _| async move {
 8698                assert_eq!(
 8699                    params.text_document.uri,
 8700                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8701                );
 8702                futures::future::pending::<()>().await;
 8703                unreachable!()
 8704            },
 8705        );
 8706        let save = editor
 8707            .update_in(cx, |editor, window, cx| {
 8708                editor.save(true, project.clone(), window, cx)
 8709            })
 8710            .unwrap();
 8711        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8712        cx.executor().start_waiting();
 8713        save.await;
 8714        assert_eq!(
 8715            editor.update(cx, |editor, cx| editor.text(cx)),
 8716            "one\ntwo\nthree\n"
 8717        );
 8718    }
 8719
 8720    // For non-dirty buffer, no formatting request should be sent
 8721    {
 8722        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8723
 8724        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8725            panic!("Should not be invoked on non-dirty buffer");
 8726        });
 8727        let save = editor
 8728            .update_in(cx, |editor, window, cx| {
 8729                editor.save(true, project.clone(), window, cx)
 8730            })
 8731            .unwrap();
 8732        cx.executor().start_waiting();
 8733        save.await;
 8734    }
 8735
 8736    // Set rust language override and assert overridden tabsize is sent to language server
 8737    update_test_language_settings(cx, |settings| {
 8738        settings.languages.insert(
 8739            "Rust".into(),
 8740            LanguageSettingsContent {
 8741                tab_size: NonZeroU32::new(8),
 8742                ..Default::default()
 8743            },
 8744        );
 8745    });
 8746
 8747    {
 8748        editor.update_in(cx, |editor, window, cx| {
 8749            editor.set_text("somehting_new\n", window, cx)
 8750        });
 8751        assert!(cx.read(|cx| editor.is_dirty(cx)));
 8752        let _formatting_request_signal = fake_server
 8753            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8754                assert_eq!(
 8755                    params.text_document.uri,
 8756                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8757                );
 8758                assert_eq!(params.options.tab_size, 8);
 8759                Ok(Some(vec![]))
 8760            });
 8761        let save = editor
 8762            .update_in(cx, |editor, window, cx| {
 8763                editor.save(true, project.clone(), window, cx)
 8764            })
 8765            .unwrap();
 8766        cx.executor().start_waiting();
 8767        save.await;
 8768    }
 8769}
 8770
 8771#[gpui::test]
 8772async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 8773    init_test(cx, |_| {});
 8774
 8775    let cols = 4;
 8776    let rows = 10;
 8777    let sample_text_1 = sample_text(rows, cols, 'a');
 8778    assert_eq!(
 8779        sample_text_1,
 8780        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8781    );
 8782    let sample_text_2 = sample_text(rows, cols, 'l');
 8783    assert_eq!(
 8784        sample_text_2,
 8785        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8786    );
 8787    let sample_text_3 = sample_text(rows, cols, 'v');
 8788    assert_eq!(
 8789        sample_text_3,
 8790        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 8791    );
 8792
 8793    let fs = FakeFs::new(cx.executor());
 8794    fs.insert_tree(
 8795        path!("/a"),
 8796        json!({
 8797            "main.rs": sample_text_1,
 8798            "other.rs": sample_text_2,
 8799            "lib.rs": sample_text_3,
 8800        }),
 8801    )
 8802    .await;
 8803
 8804    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 8805    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8806    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 8807
 8808    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8809    language_registry.add(rust_lang());
 8810    let mut fake_servers = language_registry.register_fake_lsp(
 8811        "Rust",
 8812        FakeLspAdapter {
 8813            capabilities: lsp::ServerCapabilities {
 8814                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8815                ..Default::default()
 8816            },
 8817            ..Default::default()
 8818        },
 8819    );
 8820
 8821    let worktree = project.update(cx, |project, cx| {
 8822        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 8823        assert_eq!(worktrees.len(), 1);
 8824        worktrees.pop().unwrap()
 8825    });
 8826    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 8827
 8828    let buffer_1 = project
 8829        .update(cx, |project, cx| {
 8830            project.open_buffer((worktree_id, "main.rs"), cx)
 8831        })
 8832        .await
 8833        .unwrap();
 8834    let buffer_2 = project
 8835        .update(cx, |project, cx| {
 8836            project.open_buffer((worktree_id, "other.rs"), cx)
 8837        })
 8838        .await
 8839        .unwrap();
 8840    let buffer_3 = project
 8841        .update(cx, |project, cx| {
 8842            project.open_buffer((worktree_id, "lib.rs"), cx)
 8843        })
 8844        .await
 8845        .unwrap();
 8846
 8847    let multi_buffer = cx.new(|cx| {
 8848        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 8849        multi_buffer.push_excerpts(
 8850            buffer_1.clone(),
 8851            [
 8852                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8853                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8854                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8855            ],
 8856            cx,
 8857        );
 8858        multi_buffer.push_excerpts(
 8859            buffer_2.clone(),
 8860            [
 8861                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8862                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8863                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8864            ],
 8865            cx,
 8866        );
 8867        multi_buffer.push_excerpts(
 8868            buffer_3.clone(),
 8869            [
 8870                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 8871                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 8872                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 8873            ],
 8874            cx,
 8875        );
 8876        multi_buffer
 8877    });
 8878    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 8879        Editor::new(
 8880            EditorMode::full(),
 8881            multi_buffer,
 8882            Some(project.clone()),
 8883            window,
 8884            cx,
 8885        )
 8886    });
 8887
 8888    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8889        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8890            s.select_ranges(Some(1..2))
 8891        });
 8892        editor.insert("|one|two|three|", window, cx);
 8893    });
 8894    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8895    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 8896        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 8897            s.select_ranges(Some(60..70))
 8898        });
 8899        editor.insert("|four|five|six|", window, cx);
 8900    });
 8901    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 8902
 8903    // First two buffers should be edited, but not the third one.
 8904    assert_eq!(
 8905        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8906        "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}",
 8907    );
 8908    buffer_1.update(cx, |buffer, _| {
 8909        assert!(buffer.is_dirty());
 8910        assert_eq!(
 8911            buffer.text(),
 8912            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 8913        )
 8914    });
 8915    buffer_2.update(cx, |buffer, _| {
 8916        assert!(buffer.is_dirty());
 8917        assert_eq!(
 8918            buffer.text(),
 8919            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 8920        )
 8921    });
 8922    buffer_3.update(cx, |buffer, _| {
 8923        assert!(!buffer.is_dirty());
 8924        assert_eq!(buffer.text(), sample_text_3,)
 8925    });
 8926    cx.executor().run_until_parked();
 8927
 8928    cx.executor().start_waiting();
 8929    let save = multi_buffer_editor
 8930        .update_in(cx, |editor, window, cx| {
 8931            editor.save(true, project.clone(), window, cx)
 8932        })
 8933        .unwrap();
 8934
 8935    let fake_server = fake_servers.next().await.unwrap();
 8936    fake_server
 8937        .server
 8938        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8939            Ok(Some(vec![lsp::TextEdit::new(
 8940                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8941                format!("[{} formatted]", params.text_document.uri),
 8942            )]))
 8943        })
 8944        .detach();
 8945    save.await;
 8946
 8947    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8948    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8949    assert_eq!(
 8950        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8951        uri!(
 8952            "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}"
 8953        ),
 8954    );
 8955    buffer_1.update(cx, |buffer, _| {
 8956        assert!(!buffer.is_dirty());
 8957        assert_eq!(
 8958            buffer.text(),
 8959            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8960        )
 8961    });
 8962    buffer_2.update(cx, |buffer, _| {
 8963        assert!(!buffer.is_dirty());
 8964        assert_eq!(
 8965            buffer.text(),
 8966            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8967        )
 8968    });
 8969    buffer_3.update(cx, |buffer, _| {
 8970        assert!(!buffer.is_dirty());
 8971        assert_eq!(buffer.text(), sample_text_3,)
 8972    });
 8973}
 8974
 8975#[gpui::test]
 8976async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8977    init_test(cx, |_| {});
 8978
 8979    let fs = FakeFs::new(cx.executor());
 8980    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8981
 8982    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8983
 8984    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8985    language_registry.add(rust_lang());
 8986    let mut fake_servers = language_registry.register_fake_lsp(
 8987        "Rust",
 8988        FakeLspAdapter {
 8989            capabilities: lsp::ServerCapabilities {
 8990                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8991                ..Default::default()
 8992            },
 8993            ..Default::default()
 8994        },
 8995    );
 8996
 8997    let buffer = project
 8998        .update(cx, |project, cx| {
 8999            project.open_local_buffer(path!("/file.rs"), cx)
 9000        })
 9001        .await
 9002        .unwrap();
 9003
 9004    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9005    let (editor, cx) = cx.add_window_view(|window, cx| {
 9006        build_editor_with_project(project.clone(), buffer, window, cx)
 9007    });
 9008    editor.update_in(cx, |editor, window, cx| {
 9009        editor.set_text("one\ntwo\nthree\n", window, cx)
 9010    });
 9011    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9012
 9013    cx.executor().start_waiting();
 9014    let fake_server = fake_servers.next().await.unwrap();
 9015
 9016    let save = editor
 9017        .update_in(cx, |editor, window, cx| {
 9018            editor.save(true, project.clone(), window, cx)
 9019        })
 9020        .unwrap();
 9021    fake_server
 9022        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9023            assert_eq!(
 9024                params.text_document.uri,
 9025                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9026            );
 9027            assert_eq!(params.options.tab_size, 4);
 9028            Ok(Some(vec![lsp::TextEdit::new(
 9029                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9030                ", ".to_string(),
 9031            )]))
 9032        })
 9033        .next()
 9034        .await;
 9035    cx.executor().start_waiting();
 9036    save.await;
 9037    assert_eq!(
 9038        editor.update(cx, |editor, cx| editor.text(cx)),
 9039        "one, two\nthree\n"
 9040    );
 9041    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9042
 9043    editor.update_in(cx, |editor, window, cx| {
 9044        editor.set_text("one\ntwo\nthree\n", window, cx)
 9045    });
 9046    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9047
 9048    // Ensure we can still save even if formatting hangs.
 9049    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9050        move |params, _| async move {
 9051            assert_eq!(
 9052                params.text_document.uri,
 9053                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9054            );
 9055            futures::future::pending::<()>().await;
 9056            unreachable!()
 9057        },
 9058    );
 9059    let save = editor
 9060        .update_in(cx, |editor, window, cx| {
 9061            editor.save(true, project.clone(), window, cx)
 9062        })
 9063        .unwrap();
 9064    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9065    cx.executor().start_waiting();
 9066    save.await;
 9067    assert_eq!(
 9068        editor.update(cx, |editor, cx| editor.text(cx)),
 9069        "one\ntwo\nthree\n"
 9070    );
 9071    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9072
 9073    // For non-dirty buffer, no formatting request should be sent
 9074    let save = editor
 9075        .update_in(cx, |editor, window, cx| {
 9076            editor.save(true, project.clone(), window, cx)
 9077        })
 9078        .unwrap();
 9079    let _pending_format_request = fake_server
 9080        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9081            panic!("Should not be invoked on non-dirty buffer");
 9082        })
 9083        .next();
 9084    cx.executor().start_waiting();
 9085    save.await;
 9086
 9087    // Set Rust language override and assert overridden tabsize is sent to language server
 9088    update_test_language_settings(cx, |settings| {
 9089        settings.languages.insert(
 9090            "Rust".into(),
 9091            LanguageSettingsContent {
 9092                tab_size: NonZeroU32::new(8),
 9093                ..Default::default()
 9094            },
 9095        );
 9096    });
 9097
 9098    editor.update_in(cx, |editor, window, cx| {
 9099        editor.set_text("somehting_new\n", window, cx)
 9100    });
 9101    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9102    let save = editor
 9103        .update_in(cx, |editor, window, cx| {
 9104            editor.save(true, project.clone(), window, cx)
 9105        })
 9106        .unwrap();
 9107    fake_server
 9108        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9109            assert_eq!(
 9110                params.text_document.uri,
 9111                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9112            );
 9113            assert_eq!(params.options.tab_size, 8);
 9114            Ok(Some(Vec::new()))
 9115        })
 9116        .next()
 9117        .await;
 9118    save.await;
 9119}
 9120
 9121#[gpui::test]
 9122async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9123    init_test(cx, |settings| {
 9124        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9125            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9126        ))
 9127    });
 9128
 9129    let fs = FakeFs::new(cx.executor());
 9130    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9131
 9132    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9133
 9134    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9135    language_registry.add(Arc::new(Language::new(
 9136        LanguageConfig {
 9137            name: "Rust".into(),
 9138            matcher: LanguageMatcher {
 9139                path_suffixes: vec!["rs".to_string()],
 9140                ..Default::default()
 9141            },
 9142            ..LanguageConfig::default()
 9143        },
 9144        Some(tree_sitter_rust::LANGUAGE.into()),
 9145    )));
 9146    update_test_language_settings(cx, |settings| {
 9147        // Enable Prettier formatting for the same buffer, and ensure
 9148        // LSP is called instead of Prettier.
 9149        settings.defaults.prettier = Some(PrettierSettings {
 9150            allowed: true,
 9151            ..PrettierSettings::default()
 9152        });
 9153    });
 9154    let mut fake_servers = language_registry.register_fake_lsp(
 9155        "Rust",
 9156        FakeLspAdapter {
 9157            capabilities: lsp::ServerCapabilities {
 9158                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9159                ..Default::default()
 9160            },
 9161            ..Default::default()
 9162        },
 9163    );
 9164
 9165    let buffer = project
 9166        .update(cx, |project, cx| {
 9167            project.open_local_buffer(path!("/file.rs"), cx)
 9168        })
 9169        .await
 9170        .unwrap();
 9171
 9172    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9173    let (editor, cx) = cx.add_window_view(|window, cx| {
 9174        build_editor_with_project(project.clone(), buffer, window, cx)
 9175    });
 9176    editor.update_in(cx, |editor, window, cx| {
 9177        editor.set_text("one\ntwo\nthree\n", window, cx)
 9178    });
 9179
 9180    cx.executor().start_waiting();
 9181    let fake_server = fake_servers.next().await.unwrap();
 9182
 9183    let format = editor
 9184        .update_in(cx, |editor, window, cx| {
 9185            editor.perform_format(
 9186                project.clone(),
 9187                FormatTrigger::Manual,
 9188                FormatTarget::Buffers,
 9189                window,
 9190                cx,
 9191            )
 9192        })
 9193        .unwrap();
 9194    fake_server
 9195        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9196            assert_eq!(
 9197                params.text_document.uri,
 9198                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9199            );
 9200            assert_eq!(params.options.tab_size, 4);
 9201            Ok(Some(vec![lsp::TextEdit::new(
 9202                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9203                ", ".to_string(),
 9204            )]))
 9205        })
 9206        .next()
 9207        .await;
 9208    cx.executor().start_waiting();
 9209    format.await;
 9210    assert_eq!(
 9211        editor.update(cx, |editor, cx| editor.text(cx)),
 9212        "one, two\nthree\n"
 9213    );
 9214
 9215    editor.update_in(cx, |editor, window, cx| {
 9216        editor.set_text("one\ntwo\nthree\n", window, cx)
 9217    });
 9218    // Ensure we don't lock if formatting hangs.
 9219    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9220        move |params, _| async move {
 9221            assert_eq!(
 9222                params.text_document.uri,
 9223                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9224            );
 9225            futures::future::pending::<()>().await;
 9226            unreachable!()
 9227        },
 9228    );
 9229    let format = editor
 9230        .update_in(cx, |editor, window, cx| {
 9231            editor.perform_format(
 9232                project,
 9233                FormatTrigger::Manual,
 9234                FormatTarget::Buffers,
 9235                window,
 9236                cx,
 9237            )
 9238        })
 9239        .unwrap();
 9240    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9241    cx.executor().start_waiting();
 9242    format.await;
 9243    assert_eq!(
 9244        editor.update(cx, |editor, cx| editor.text(cx)),
 9245        "one\ntwo\nthree\n"
 9246    );
 9247}
 9248
 9249#[gpui::test]
 9250async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9251    init_test(cx, |settings| {
 9252        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9253        settings.defaults.formatter =
 9254            Some(language_settings::SelectedFormatter::List(FormatterList(
 9255                vec![
 9256                    Formatter::LanguageServer { name: None },
 9257                    Formatter::CodeActions(
 9258                        [
 9259                            ("code-action-1".into(), true),
 9260                            ("code-action-2".into(), true),
 9261                        ]
 9262                        .into_iter()
 9263                        .collect(),
 9264                    ),
 9265                ]
 9266                .into(),
 9267            )))
 9268    });
 9269
 9270    let fs = FakeFs::new(cx.executor());
 9271    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9272        .await;
 9273
 9274    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9275    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9276    language_registry.add(rust_lang());
 9277
 9278    let mut fake_servers = language_registry.register_fake_lsp(
 9279        "Rust",
 9280        FakeLspAdapter {
 9281            capabilities: lsp::ServerCapabilities {
 9282                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9283                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9284                    commands: vec!["the-command-for-code-action-1".into()],
 9285                    ..Default::default()
 9286                }),
 9287                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9288                ..Default::default()
 9289            },
 9290            ..Default::default()
 9291        },
 9292    );
 9293
 9294    let buffer = project
 9295        .update(cx, |project, cx| {
 9296            project.open_local_buffer(path!("/file.rs"), cx)
 9297        })
 9298        .await
 9299        .unwrap();
 9300
 9301    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9302    let (editor, cx) = cx.add_window_view(|window, cx| {
 9303        build_editor_with_project(project.clone(), buffer, window, cx)
 9304    });
 9305
 9306    cx.executor().start_waiting();
 9307
 9308    let fake_server = fake_servers.next().await.unwrap();
 9309    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9310        move |_params, _| async move {
 9311            Ok(Some(vec![lsp::TextEdit::new(
 9312                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9313                "applied-formatting\n".to_string(),
 9314            )]))
 9315        },
 9316    );
 9317    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9318        move |params, _| async move {
 9319            assert_eq!(
 9320                params.context.only,
 9321                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9322            );
 9323            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9324            Ok(Some(vec![
 9325                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9326                    kind: Some("code-action-1".into()),
 9327                    edit: Some(lsp::WorkspaceEdit::new(
 9328                        [(
 9329                            uri.clone(),
 9330                            vec![lsp::TextEdit::new(
 9331                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9332                                "applied-code-action-1-edit\n".to_string(),
 9333                            )],
 9334                        )]
 9335                        .into_iter()
 9336                        .collect(),
 9337                    )),
 9338                    command: Some(lsp::Command {
 9339                        command: "the-command-for-code-action-1".into(),
 9340                        ..Default::default()
 9341                    }),
 9342                    ..Default::default()
 9343                }),
 9344                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9345                    kind: Some("code-action-2".into()),
 9346                    edit: Some(lsp::WorkspaceEdit::new(
 9347                        [(
 9348                            uri.clone(),
 9349                            vec![lsp::TextEdit::new(
 9350                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9351                                "applied-code-action-2-edit\n".to_string(),
 9352                            )],
 9353                        )]
 9354                        .into_iter()
 9355                        .collect(),
 9356                    )),
 9357                    ..Default::default()
 9358                }),
 9359            ]))
 9360        },
 9361    );
 9362
 9363    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9364        move |params, _| async move { Ok(params) }
 9365    });
 9366
 9367    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9368    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9369        let fake = fake_server.clone();
 9370        let lock = command_lock.clone();
 9371        move |params, _| {
 9372            assert_eq!(params.command, "the-command-for-code-action-1");
 9373            let fake = fake.clone();
 9374            let lock = lock.clone();
 9375            async move {
 9376                lock.lock().await;
 9377                fake.server
 9378                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9379                        label: None,
 9380                        edit: lsp::WorkspaceEdit {
 9381                            changes: Some(
 9382                                [(
 9383                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9384                                    vec![lsp::TextEdit {
 9385                                        range: lsp::Range::new(
 9386                                            lsp::Position::new(0, 0),
 9387                                            lsp::Position::new(0, 0),
 9388                                        ),
 9389                                        new_text: "applied-code-action-1-command\n".into(),
 9390                                    }],
 9391                                )]
 9392                                .into_iter()
 9393                                .collect(),
 9394                            ),
 9395                            ..Default::default()
 9396                        },
 9397                    })
 9398                    .await
 9399                    .into_response()
 9400                    .unwrap();
 9401                Ok(Some(json!(null)))
 9402            }
 9403        }
 9404    });
 9405
 9406    cx.executor().start_waiting();
 9407    editor
 9408        .update_in(cx, |editor, window, cx| {
 9409            editor.perform_format(
 9410                project.clone(),
 9411                FormatTrigger::Manual,
 9412                FormatTarget::Buffers,
 9413                window,
 9414                cx,
 9415            )
 9416        })
 9417        .unwrap()
 9418        .await;
 9419    editor.update(cx, |editor, cx| {
 9420        assert_eq!(
 9421            editor.text(cx),
 9422            r#"
 9423                applied-code-action-2-edit
 9424                applied-code-action-1-command
 9425                applied-code-action-1-edit
 9426                applied-formatting
 9427                one
 9428                two
 9429                three
 9430            "#
 9431            .unindent()
 9432        );
 9433    });
 9434
 9435    editor.update_in(cx, |editor, window, cx| {
 9436        editor.undo(&Default::default(), window, cx);
 9437        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9438    });
 9439
 9440    // Perform a manual edit while waiting for an LSP command
 9441    // that's being run as part of a formatting code action.
 9442    let lock_guard = command_lock.lock().await;
 9443    let format = editor
 9444        .update_in(cx, |editor, window, cx| {
 9445            editor.perform_format(
 9446                project.clone(),
 9447                FormatTrigger::Manual,
 9448                FormatTarget::Buffers,
 9449                window,
 9450                cx,
 9451            )
 9452        })
 9453        .unwrap();
 9454    cx.run_until_parked();
 9455    editor.update(cx, |editor, cx| {
 9456        assert_eq!(
 9457            editor.text(cx),
 9458            r#"
 9459                applied-code-action-1-edit
 9460                applied-formatting
 9461                one
 9462                two
 9463                three
 9464            "#
 9465            .unindent()
 9466        );
 9467
 9468        editor.buffer.update(cx, |buffer, cx| {
 9469            let ix = buffer.len(cx);
 9470            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9471        });
 9472    });
 9473
 9474    // Allow the LSP command to proceed. Because the buffer was edited,
 9475    // the second code action will not be run.
 9476    drop(lock_guard);
 9477    format.await;
 9478    editor.update_in(cx, |editor, window, cx| {
 9479        assert_eq!(
 9480            editor.text(cx),
 9481            r#"
 9482                applied-code-action-1-command
 9483                applied-code-action-1-edit
 9484                applied-formatting
 9485                one
 9486                two
 9487                three
 9488                edited
 9489            "#
 9490            .unindent()
 9491        );
 9492
 9493        // The manual edit is undone first, because it is the last thing the user did
 9494        // (even though the command completed afterwards).
 9495        editor.undo(&Default::default(), window, cx);
 9496        assert_eq!(
 9497            editor.text(cx),
 9498            r#"
 9499                applied-code-action-1-command
 9500                applied-code-action-1-edit
 9501                applied-formatting
 9502                one
 9503                two
 9504                three
 9505            "#
 9506            .unindent()
 9507        );
 9508
 9509        // All the formatting (including the command, which completed after the manual edit)
 9510        // is undone together.
 9511        editor.undo(&Default::default(), window, cx);
 9512        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9513    });
 9514}
 9515
 9516#[gpui::test]
 9517async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9518    init_test(cx, |settings| {
 9519        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9520            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9521        ))
 9522    });
 9523
 9524    let fs = FakeFs::new(cx.executor());
 9525    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9526
 9527    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9528
 9529    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9530    language_registry.add(Arc::new(Language::new(
 9531        LanguageConfig {
 9532            name: "TypeScript".into(),
 9533            matcher: LanguageMatcher {
 9534                path_suffixes: vec!["ts".to_string()],
 9535                ..Default::default()
 9536            },
 9537            ..LanguageConfig::default()
 9538        },
 9539        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9540    )));
 9541    update_test_language_settings(cx, |settings| {
 9542        settings.defaults.prettier = Some(PrettierSettings {
 9543            allowed: true,
 9544            ..PrettierSettings::default()
 9545        });
 9546    });
 9547    let mut fake_servers = language_registry.register_fake_lsp(
 9548        "TypeScript",
 9549        FakeLspAdapter {
 9550            capabilities: lsp::ServerCapabilities {
 9551                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9552                ..Default::default()
 9553            },
 9554            ..Default::default()
 9555        },
 9556    );
 9557
 9558    let buffer = project
 9559        .update(cx, |project, cx| {
 9560            project.open_local_buffer(path!("/file.ts"), cx)
 9561        })
 9562        .await
 9563        .unwrap();
 9564
 9565    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9566    let (editor, cx) = cx.add_window_view(|window, cx| {
 9567        build_editor_with_project(project.clone(), buffer, window, cx)
 9568    });
 9569    editor.update_in(cx, |editor, window, cx| {
 9570        editor.set_text(
 9571            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9572            window,
 9573            cx,
 9574        )
 9575    });
 9576
 9577    cx.executor().start_waiting();
 9578    let fake_server = fake_servers.next().await.unwrap();
 9579
 9580    let format = editor
 9581        .update_in(cx, |editor, window, cx| {
 9582            editor.perform_code_action_kind(
 9583                project.clone(),
 9584                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9585                window,
 9586                cx,
 9587            )
 9588        })
 9589        .unwrap();
 9590    fake_server
 9591        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9592            assert_eq!(
 9593                params.text_document.uri,
 9594                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9595            );
 9596            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9597                lsp::CodeAction {
 9598                    title: "Organize Imports".to_string(),
 9599                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9600                    edit: Some(lsp::WorkspaceEdit {
 9601                        changes: Some(
 9602                            [(
 9603                                params.text_document.uri.clone(),
 9604                                vec![lsp::TextEdit::new(
 9605                                    lsp::Range::new(
 9606                                        lsp::Position::new(1, 0),
 9607                                        lsp::Position::new(2, 0),
 9608                                    ),
 9609                                    "".to_string(),
 9610                                )],
 9611                            )]
 9612                            .into_iter()
 9613                            .collect(),
 9614                        ),
 9615                        ..Default::default()
 9616                    }),
 9617                    ..Default::default()
 9618                },
 9619            )]))
 9620        })
 9621        .next()
 9622        .await;
 9623    cx.executor().start_waiting();
 9624    format.await;
 9625    assert_eq!(
 9626        editor.update(cx, |editor, cx| editor.text(cx)),
 9627        "import { a } from 'module';\n\nconst x = a;\n"
 9628    );
 9629
 9630    editor.update_in(cx, |editor, window, cx| {
 9631        editor.set_text(
 9632            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9633            window,
 9634            cx,
 9635        )
 9636    });
 9637    // Ensure we don't lock if code action hangs.
 9638    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9639        move |params, _| async move {
 9640            assert_eq!(
 9641                params.text_document.uri,
 9642                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9643            );
 9644            futures::future::pending::<()>().await;
 9645            unreachable!()
 9646        },
 9647    );
 9648    let format = editor
 9649        .update_in(cx, |editor, window, cx| {
 9650            editor.perform_code_action_kind(
 9651                project,
 9652                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9653                window,
 9654                cx,
 9655            )
 9656        })
 9657        .unwrap();
 9658    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9659    cx.executor().start_waiting();
 9660    format.await;
 9661    assert_eq!(
 9662        editor.update(cx, |editor, cx| editor.text(cx)),
 9663        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9664    );
 9665}
 9666
 9667#[gpui::test]
 9668async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9669    init_test(cx, |_| {});
 9670
 9671    let mut cx = EditorLspTestContext::new_rust(
 9672        lsp::ServerCapabilities {
 9673            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9674            ..Default::default()
 9675        },
 9676        cx,
 9677    )
 9678    .await;
 9679
 9680    cx.set_state(indoc! {"
 9681        one.twoˇ
 9682    "});
 9683
 9684    // The format request takes a long time. When it completes, it inserts
 9685    // a newline and an indent before the `.`
 9686    cx.lsp
 9687        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9688            let executor = cx.background_executor().clone();
 9689            async move {
 9690                executor.timer(Duration::from_millis(100)).await;
 9691                Ok(Some(vec![lsp::TextEdit {
 9692                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9693                    new_text: "\n    ".into(),
 9694                }]))
 9695            }
 9696        });
 9697
 9698    // Submit a format request.
 9699    let format_1 = cx
 9700        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9701        .unwrap();
 9702    cx.executor().run_until_parked();
 9703
 9704    // Submit a second format request.
 9705    let format_2 = cx
 9706        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9707        .unwrap();
 9708    cx.executor().run_until_parked();
 9709
 9710    // Wait for both format requests to complete
 9711    cx.executor().advance_clock(Duration::from_millis(200));
 9712    cx.executor().start_waiting();
 9713    format_1.await.unwrap();
 9714    cx.executor().start_waiting();
 9715    format_2.await.unwrap();
 9716
 9717    // The formatting edits only happens once.
 9718    cx.assert_editor_state(indoc! {"
 9719        one
 9720            .twoˇ
 9721    "});
 9722}
 9723
 9724#[gpui::test]
 9725async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9726    init_test(cx, |settings| {
 9727        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9728    });
 9729
 9730    let mut cx = EditorLspTestContext::new_rust(
 9731        lsp::ServerCapabilities {
 9732            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9733            ..Default::default()
 9734        },
 9735        cx,
 9736    )
 9737    .await;
 9738
 9739    // Set up a buffer white some trailing whitespace and no trailing newline.
 9740    cx.set_state(
 9741        &[
 9742            "one ",   //
 9743            "twoˇ",   //
 9744            "three ", //
 9745            "four",   //
 9746        ]
 9747        .join("\n"),
 9748    );
 9749
 9750    // Submit a format request.
 9751    let format = cx
 9752        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9753        .unwrap();
 9754
 9755    // Record which buffer changes have been sent to the language server
 9756    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9757    cx.lsp
 9758        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9759            let buffer_changes = buffer_changes.clone();
 9760            move |params, _| {
 9761                buffer_changes.lock().extend(
 9762                    params
 9763                        .content_changes
 9764                        .into_iter()
 9765                        .map(|e| (e.range.unwrap(), e.text)),
 9766                );
 9767            }
 9768        });
 9769
 9770    // Handle formatting requests to the language server.
 9771    cx.lsp
 9772        .set_request_handler::<lsp::request::Formatting, _, _>({
 9773            let buffer_changes = buffer_changes.clone();
 9774            move |_, _| {
 9775                // When formatting is requested, trailing whitespace has already been stripped,
 9776                // and the trailing newline has already been added.
 9777                assert_eq!(
 9778                    &buffer_changes.lock()[1..],
 9779                    &[
 9780                        (
 9781                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9782                            "".into()
 9783                        ),
 9784                        (
 9785                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9786                            "".into()
 9787                        ),
 9788                        (
 9789                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9790                            "\n".into()
 9791                        ),
 9792                    ]
 9793                );
 9794
 9795                // Insert blank lines between each line of the buffer.
 9796                async move {
 9797                    Ok(Some(vec![
 9798                        lsp::TextEdit {
 9799                            range: lsp::Range::new(
 9800                                lsp::Position::new(1, 0),
 9801                                lsp::Position::new(1, 0),
 9802                            ),
 9803                            new_text: "\n".into(),
 9804                        },
 9805                        lsp::TextEdit {
 9806                            range: lsp::Range::new(
 9807                                lsp::Position::new(2, 0),
 9808                                lsp::Position::new(2, 0),
 9809                            ),
 9810                            new_text: "\n".into(),
 9811                        },
 9812                    ]))
 9813                }
 9814            }
 9815        });
 9816
 9817    // After formatting the buffer, the trailing whitespace is stripped,
 9818    // a newline is appended, and the edits provided by the language server
 9819    // have been applied.
 9820    format.await.unwrap();
 9821    cx.assert_editor_state(
 9822        &[
 9823            "one",   //
 9824            "",      //
 9825            "twoˇ",  //
 9826            "",      //
 9827            "three", //
 9828            "four",  //
 9829            "",      //
 9830        ]
 9831        .join("\n"),
 9832    );
 9833
 9834    // Undoing the formatting undoes the trailing whitespace removal, the
 9835    // trailing newline, and the LSP edits.
 9836    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9837    cx.assert_editor_state(
 9838        &[
 9839            "one ",   //
 9840            "twoˇ",   //
 9841            "three ", //
 9842            "four",   //
 9843        ]
 9844        .join("\n"),
 9845    );
 9846}
 9847
 9848#[gpui::test]
 9849async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9850    cx: &mut TestAppContext,
 9851) {
 9852    init_test(cx, |_| {});
 9853
 9854    cx.update(|cx| {
 9855        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9856            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9857                settings.auto_signature_help = Some(true);
 9858            });
 9859        });
 9860    });
 9861
 9862    let mut cx = EditorLspTestContext::new_rust(
 9863        lsp::ServerCapabilities {
 9864            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9865                ..Default::default()
 9866            }),
 9867            ..Default::default()
 9868        },
 9869        cx,
 9870    )
 9871    .await;
 9872
 9873    let language = Language::new(
 9874        LanguageConfig {
 9875            name: "Rust".into(),
 9876            brackets: BracketPairConfig {
 9877                pairs: vec![
 9878                    BracketPair {
 9879                        start: "{".to_string(),
 9880                        end: "}".to_string(),
 9881                        close: true,
 9882                        surround: true,
 9883                        newline: true,
 9884                    },
 9885                    BracketPair {
 9886                        start: "(".to_string(),
 9887                        end: ")".to_string(),
 9888                        close: true,
 9889                        surround: true,
 9890                        newline: true,
 9891                    },
 9892                    BracketPair {
 9893                        start: "/*".to_string(),
 9894                        end: " */".to_string(),
 9895                        close: true,
 9896                        surround: true,
 9897                        newline: true,
 9898                    },
 9899                    BracketPair {
 9900                        start: "[".to_string(),
 9901                        end: "]".to_string(),
 9902                        close: false,
 9903                        surround: false,
 9904                        newline: true,
 9905                    },
 9906                    BracketPair {
 9907                        start: "\"".to_string(),
 9908                        end: "\"".to_string(),
 9909                        close: true,
 9910                        surround: true,
 9911                        newline: false,
 9912                    },
 9913                    BracketPair {
 9914                        start: "<".to_string(),
 9915                        end: ">".to_string(),
 9916                        close: false,
 9917                        surround: true,
 9918                        newline: true,
 9919                    },
 9920                ],
 9921                ..Default::default()
 9922            },
 9923            autoclose_before: "})]".to_string(),
 9924            ..Default::default()
 9925        },
 9926        Some(tree_sitter_rust::LANGUAGE.into()),
 9927    );
 9928    let language = Arc::new(language);
 9929
 9930    cx.language_registry().add(language.clone());
 9931    cx.update_buffer(|buffer, cx| {
 9932        buffer.set_language(Some(language), cx);
 9933    });
 9934
 9935    cx.set_state(
 9936        &r#"
 9937            fn main() {
 9938                sampleˇ
 9939            }
 9940        "#
 9941        .unindent(),
 9942    );
 9943
 9944    cx.update_editor(|editor, window, cx| {
 9945        editor.handle_input("(", window, cx);
 9946    });
 9947    cx.assert_editor_state(
 9948        &"
 9949            fn main() {
 9950                sample(ˇ)
 9951            }
 9952        "
 9953        .unindent(),
 9954    );
 9955
 9956    let mocked_response = lsp::SignatureHelp {
 9957        signatures: vec![lsp::SignatureInformation {
 9958            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9959            documentation: None,
 9960            parameters: Some(vec![
 9961                lsp::ParameterInformation {
 9962                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9963                    documentation: None,
 9964                },
 9965                lsp::ParameterInformation {
 9966                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9967                    documentation: None,
 9968                },
 9969            ]),
 9970            active_parameter: None,
 9971        }],
 9972        active_signature: Some(0),
 9973        active_parameter: Some(0),
 9974    };
 9975    handle_signature_help_request(&mut cx, mocked_response).await;
 9976
 9977    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9978        .await;
 9979
 9980    cx.editor(|editor, _, _| {
 9981        let signature_help_state = editor.signature_help_state.popover().cloned();
 9982        assert_eq!(
 9983            signature_help_state.unwrap().label,
 9984            "param1: u8, param2: u8"
 9985        );
 9986    });
 9987}
 9988
 9989#[gpui::test]
 9990async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9991    init_test(cx, |_| {});
 9992
 9993    cx.update(|cx| {
 9994        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9995            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9996                settings.auto_signature_help = Some(false);
 9997                settings.show_signature_help_after_edits = Some(false);
 9998            });
 9999        });
10000    });
10001
10002    let mut cx = EditorLspTestContext::new_rust(
10003        lsp::ServerCapabilities {
10004            signature_help_provider: Some(lsp::SignatureHelpOptions {
10005                ..Default::default()
10006            }),
10007            ..Default::default()
10008        },
10009        cx,
10010    )
10011    .await;
10012
10013    let language = Language::new(
10014        LanguageConfig {
10015            name: "Rust".into(),
10016            brackets: BracketPairConfig {
10017                pairs: vec![
10018                    BracketPair {
10019                        start: "{".to_string(),
10020                        end: "}".to_string(),
10021                        close: true,
10022                        surround: true,
10023                        newline: true,
10024                    },
10025                    BracketPair {
10026                        start: "(".to_string(),
10027                        end: ")".to_string(),
10028                        close: true,
10029                        surround: true,
10030                        newline: true,
10031                    },
10032                    BracketPair {
10033                        start: "/*".to_string(),
10034                        end: " */".to_string(),
10035                        close: true,
10036                        surround: true,
10037                        newline: true,
10038                    },
10039                    BracketPair {
10040                        start: "[".to_string(),
10041                        end: "]".to_string(),
10042                        close: false,
10043                        surround: false,
10044                        newline: true,
10045                    },
10046                    BracketPair {
10047                        start: "\"".to_string(),
10048                        end: "\"".to_string(),
10049                        close: true,
10050                        surround: true,
10051                        newline: false,
10052                    },
10053                    BracketPair {
10054                        start: "<".to_string(),
10055                        end: ">".to_string(),
10056                        close: false,
10057                        surround: true,
10058                        newline: true,
10059                    },
10060                ],
10061                ..Default::default()
10062            },
10063            autoclose_before: "})]".to_string(),
10064            ..Default::default()
10065        },
10066        Some(tree_sitter_rust::LANGUAGE.into()),
10067    );
10068    let language = Arc::new(language);
10069
10070    cx.language_registry().add(language.clone());
10071    cx.update_buffer(|buffer, cx| {
10072        buffer.set_language(Some(language), cx);
10073    });
10074
10075    // Ensure that signature_help is not called when no signature help is enabled.
10076    cx.set_state(
10077        &r#"
10078            fn main() {
10079                sampleˇ
10080            }
10081        "#
10082        .unindent(),
10083    );
10084    cx.update_editor(|editor, window, cx| {
10085        editor.handle_input("(", window, cx);
10086    });
10087    cx.assert_editor_state(
10088        &"
10089            fn main() {
10090                sample(ˇ)
10091            }
10092        "
10093        .unindent(),
10094    );
10095    cx.editor(|editor, _, _| {
10096        assert!(editor.signature_help_state.task().is_none());
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
10119    // Ensure that signature_help is called when enabled afte edits
10120    cx.update(|_, cx| {
10121        cx.update_global::<SettingsStore, _>(|settings, cx| {
10122            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10123                settings.auto_signature_help = Some(false);
10124                settings.show_signature_help_after_edits = Some(true);
10125            });
10126        });
10127    });
10128    cx.set_state(
10129        &r#"
10130            fn main() {
10131                sampleˇ
10132            }
10133        "#
10134        .unindent(),
10135    );
10136    cx.update_editor(|editor, window, cx| {
10137        editor.handle_input("(", window, cx);
10138    });
10139    cx.assert_editor_state(
10140        &"
10141            fn main() {
10142                sample(ˇ)
10143            }
10144        "
10145        .unindent(),
10146    );
10147    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10148    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10149        .await;
10150    cx.update_editor(|editor, _, _| {
10151        let signature_help_state = editor.signature_help_state.popover().cloned();
10152        assert!(signature_help_state.is_some());
10153        assert_eq!(
10154            signature_help_state.unwrap().label,
10155            "param1: u8, param2: u8"
10156        );
10157        editor.signature_help_state = SignatureHelpState::default();
10158    });
10159
10160    // Ensure that signature_help is called when auto signature help override is enabled
10161    cx.update(|_, cx| {
10162        cx.update_global::<SettingsStore, _>(|settings, cx| {
10163            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10164                settings.auto_signature_help = Some(true);
10165                settings.show_signature_help_after_edits = Some(false);
10166            });
10167        });
10168    });
10169    cx.set_state(
10170        &r#"
10171            fn main() {
10172                sampleˇ
10173            }
10174        "#
10175        .unindent(),
10176    );
10177    cx.update_editor(|editor, window, cx| {
10178        editor.handle_input("(", window, cx);
10179    });
10180    cx.assert_editor_state(
10181        &"
10182            fn main() {
10183                sample(ˇ)
10184            }
10185        "
10186        .unindent(),
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        let signature_help_state = editor.signature_help_state.popover().cloned();
10193        assert!(signature_help_state.is_some());
10194        assert_eq!(
10195            signature_help_state.unwrap().label,
10196            "param1: u8, param2: u8"
10197        );
10198    });
10199}
10200
10201#[gpui::test]
10202async fn test_signature_help(cx: &mut TestAppContext) {
10203    init_test(cx, |_| {});
10204    cx.update(|cx| {
10205        cx.update_global::<SettingsStore, _>(|settings, cx| {
10206            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10207                settings.auto_signature_help = Some(true);
10208            });
10209        });
10210    });
10211
10212    let mut cx = EditorLspTestContext::new_rust(
10213        lsp::ServerCapabilities {
10214            signature_help_provider: Some(lsp::SignatureHelpOptions {
10215                ..Default::default()
10216            }),
10217            ..Default::default()
10218        },
10219        cx,
10220    )
10221    .await;
10222
10223    // A test that directly calls `show_signature_help`
10224    cx.update_editor(|editor, window, cx| {
10225        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10226    });
10227
10228    let mocked_response = lsp::SignatureHelp {
10229        signatures: vec![lsp::SignatureInformation {
10230            label: "fn sample(param1: u8, param2: u8)".to_string(),
10231            documentation: None,
10232            parameters: Some(vec![
10233                lsp::ParameterInformation {
10234                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10235                    documentation: None,
10236                },
10237                lsp::ParameterInformation {
10238                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10239                    documentation: None,
10240                },
10241            ]),
10242            active_parameter: None,
10243        }],
10244        active_signature: Some(0),
10245        active_parameter: Some(0),
10246    };
10247    handle_signature_help_request(&mut cx, mocked_response).await;
10248
10249    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10250        .await;
10251
10252    cx.editor(|editor, _, _| {
10253        let signature_help_state = editor.signature_help_state.popover().cloned();
10254        assert!(signature_help_state.is_some());
10255        assert_eq!(
10256            signature_help_state.unwrap().label,
10257            "param1: u8, param2: u8"
10258        );
10259    });
10260
10261    // When exiting outside from inside the brackets, `signature_help` is closed.
10262    cx.set_state(indoc! {"
10263        fn main() {
10264            sample(ˇ);
10265        }
10266
10267        fn sample(param1: u8, param2: u8) {}
10268    "});
10269
10270    cx.update_editor(|editor, window, cx| {
10271        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10272    });
10273
10274    let mocked_response = lsp::SignatureHelp {
10275        signatures: Vec::new(),
10276        active_signature: None,
10277        active_parameter: None,
10278    };
10279    handle_signature_help_request(&mut cx, mocked_response).await;
10280
10281    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10282        .await;
10283
10284    cx.editor(|editor, _, _| {
10285        assert!(!editor.signature_help_state.is_shown());
10286    });
10287
10288    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10289    cx.set_state(indoc! {"
10290        fn main() {
10291            sample(ˇ);
10292        }
10293
10294        fn sample(param1: u8, param2: u8) {}
10295    "});
10296
10297    let mocked_response = lsp::SignatureHelp {
10298        signatures: vec![lsp::SignatureInformation {
10299            label: "fn sample(param1: u8, param2: u8)".to_string(),
10300            documentation: None,
10301            parameters: Some(vec![
10302                lsp::ParameterInformation {
10303                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10304                    documentation: None,
10305                },
10306                lsp::ParameterInformation {
10307                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10308                    documentation: None,
10309                },
10310            ]),
10311            active_parameter: None,
10312        }],
10313        active_signature: Some(0),
10314        active_parameter: Some(0),
10315    };
10316    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10317    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10318        .await;
10319    cx.editor(|editor, _, _| {
10320        assert!(editor.signature_help_state.is_shown());
10321    });
10322
10323    // Restore the popover with more parameter input
10324    cx.set_state(indoc! {"
10325        fn main() {
10326            sample(param1, param2ˇ);
10327        }
10328
10329        fn sample(param1: u8, param2: u8) {}
10330    "});
10331
10332    let mocked_response = lsp::SignatureHelp {
10333        signatures: vec![lsp::SignatureInformation {
10334            label: "fn sample(param1: u8, param2: u8)".to_string(),
10335            documentation: None,
10336            parameters: Some(vec![
10337                lsp::ParameterInformation {
10338                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10339                    documentation: None,
10340                },
10341                lsp::ParameterInformation {
10342                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10343                    documentation: None,
10344                },
10345            ]),
10346            active_parameter: None,
10347        }],
10348        active_signature: Some(0),
10349        active_parameter: Some(1),
10350    };
10351    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10352    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10353        .await;
10354
10355    // When selecting a range, the popover is gone.
10356    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10357    cx.update_editor(|editor, window, cx| {
10358        editor.change_selections(None, window, cx, |s| {
10359            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10360        })
10361    });
10362    cx.assert_editor_state(indoc! {"
10363        fn main() {
10364            sample(param1, «ˇparam2»);
10365        }
10366
10367        fn sample(param1: u8, param2: u8) {}
10368    "});
10369    cx.editor(|editor, _, _| {
10370        assert!(!editor.signature_help_state.is_shown());
10371    });
10372
10373    // When unselecting again, the popover is back if within the brackets.
10374    cx.update_editor(|editor, window, cx| {
10375        editor.change_selections(None, window, cx, |s| {
10376            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10377        })
10378    });
10379    cx.assert_editor_state(indoc! {"
10380        fn main() {
10381            sample(param1, ˇparam2);
10382        }
10383
10384        fn sample(param1: u8, param2: u8) {}
10385    "});
10386    handle_signature_help_request(&mut cx, mocked_response).await;
10387    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10388        .await;
10389    cx.editor(|editor, _, _| {
10390        assert!(editor.signature_help_state.is_shown());
10391    });
10392
10393    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10394    cx.update_editor(|editor, window, cx| {
10395        editor.change_selections(None, window, cx, |s| {
10396            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10397            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10398        })
10399    });
10400    cx.assert_editor_state(indoc! {"
10401        fn main() {
10402            sample(param1, ˇparam2);
10403        }
10404
10405        fn sample(param1: u8, param2: u8) {}
10406    "});
10407
10408    let mocked_response = lsp::SignatureHelp {
10409        signatures: vec![lsp::SignatureInformation {
10410            label: "fn sample(param1: u8, param2: u8)".to_string(),
10411            documentation: None,
10412            parameters: Some(vec![
10413                lsp::ParameterInformation {
10414                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10415                    documentation: None,
10416                },
10417                lsp::ParameterInformation {
10418                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10419                    documentation: None,
10420                },
10421            ]),
10422            active_parameter: None,
10423        }],
10424        active_signature: Some(0),
10425        active_parameter: Some(1),
10426    };
10427    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10428    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10429        .await;
10430    cx.update_editor(|editor, _, cx| {
10431        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10432    });
10433    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10434        .await;
10435    cx.update_editor(|editor, window, cx| {
10436        editor.change_selections(None, window, cx, |s| {
10437            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10438        })
10439    });
10440    cx.assert_editor_state(indoc! {"
10441        fn main() {
10442            sample(param1, «ˇparam2»);
10443        }
10444
10445        fn sample(param1: u8, param2: u8) {}
10446    "});
10447    cx.update_editor(|editor, window, cx| {
10448        editor.change_selections(None, window, cx, |s| {
10449            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10450        })
10451    });
10452    cx.assert_editor_state(indoc! {"
10453        fn main() {
10454            sample(param1, ˇparam2);
10455        }
10456
10457        fn sample(param1: u8, param2: u8) {}
10458    "});
10459    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10460        .await;
10461}
10462
10463#[gpui::test]
10464async fn test_completion_mode(cx: &mut TestAppContext) {
10465    init_test(cx, |_| {});
10466    let mut cx = EditorLspTestContext::new_rust(
10467        lsp::ServerCapabilities {
10468            completion_provider: Some(lsp::CompletionOptions {
10469                resolve_provider: Some(true),
10470                ..Default::default()
10471            }),
10472            ..Default::default()
10473        },
10474        cx,
10475    )
10476    .await;
10477
10478    struct Run {
10479        run_description: &'static str,
10480        initial_state: String,
10481        buffer_marked_text: String,
10482        completion_text: &'static str,
10483        expected_with_insert_mode: String,
10484        expected_with_replace_mode: String,
10485        expected_with_replace_subsequence_mode: String,
10486        expected_with_replace_suffix_mode: String,
10487    }
10488
10489    let runs = [
10490        Run {
10491            run_description: "Start of word matches completion text",
10492            initial_state: "before ediˇ after".into(),
10493            buffer_marked_text: "before <edi|> after".into(),
10494            completion_text: "editor",
10495            expected_with_insert_mode: "before editorˇ after".into(),
10496            expected_with_replace_mode: "before editorˇ after".into(),
10497            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10498            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10499        },
10500        Run {
10501            run_description: "Accept same text at the middle of the word",
10502            initial_state: "before ediˇtor after".into(),
10503            buffer_marked_text: "before <edi|tor> after".into(),
10504            completion_text: "editor",
10505            expected_with_insert_mode: "before editorˇtor after".into(),
10506            expected_with_replace_mode: "before editorˇ after".into(),
10507            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10508            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10509        },
10510        Run {
10511            run_description: "End of word matches completion text -- cursor at end",
10512            initial_state: "before torˇ after".into(),
10513            buffer_marked_text: "before <tor|> after".into(),
10514            completion_text: "editor",
10515            expected_with_insert_mode: "before editorˇ after".into(),
10516            expected_with_replace_mode: "before editorˇ after".into(),
10517            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10518            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10519        },
10520        Run {
10521            run_description: "End of word matches completion text -- cursor at start",
10522            initial_state: "before ˇtor after".into(),
10523            buffer_marked_text: "before <|tor> after".into(),
10524            completion_text: "editor",
10525            expected_with_insert_mode: "before editorˇtor after".into(),
10526            expected_with_replace_mode: "before editorˇ after".into(),
10527            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10528            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10529        },
10530        Run {
10531            run_description: "Prepend text containing whitespace",
10532            initial_state: "pˇfield: bool".into(),
10533            buffer_marked_text: "<p|field>: bool".into(),
10534            completion_text: "pub ",
10535            expected_with_insert_mode: "pub ˇfield: bool".into(),
10536            expected_with_replace_mode: "pub ˇ: bool".into(),
10537            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10538            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10539        },
10540        Run {
10541            run_description: "Add element to start of list",
10542            initial_state: "[element_ˇelement_2]".into(),
10543            buffer_marked_text: "[<element_|element_2>]".into(),
10544            completion_text: "element_1",
10545            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10546            expected_with_replace_mode: "[element_1ˇ]".into(),
10547            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10548            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10549        },
10550        Run {
10551            run_description: "Add element to start of list -- first and second elements are equal",
10552            initial_state: "[elˇelement]".into(),
10553            buffer_marked_text: "[<el|element>]".into(),
10554            completion_text: "element",
10555            expected_with_insert_mode: "[elementˇelement]".into(),
10556            expected_with_replace_mode: "[elementˇ]".into(),
10557            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10558            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10559        },
10560        Run {
10561            run_description: "Ends with matching suffix",
10562            initial_state: "SubˇError".into(),
10563            buffer_marked_text: "<Sub|Error>".into(),
10564            completion_text: "SubscriptionError",
10565            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10566            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10567            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10568            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10569        },
10570        Run {
10571            run_description: "Suffix is a subsequence -- contiguous",
10572            initial_state: "SubˇErr".into(),
10573            buffer_marked_text: "<Sub|Err>".into(),
10574            completion_text: "SubscriptionError",
10575            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10576            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10577            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10578            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10579        },
10580        Run {
10581            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10582            initial_state: "Suˇscrirr".into(),
10583            buffer_marked_text: "<Su|scrirr>".into(),
10584            completion_text: "SubscriptionError",
10585            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10586            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10587            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10588            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10589        },
10590        Run {
10591            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10592            initial_state: "foo(indˇix)".into(),
10593            buffer_marked_text: "foo(<ind|ix>)".into(),
10594            completion_text: "node_index",
10595            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10596            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10597            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10598            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10599        },
10600    ];
10601
10602    for run in runs {
10603        let run_variations = [
10604            (LspInsertMode::Insert, run.expected_with_insert_mode),
10605            (LspInsertMode::Replace, run.expected_with_replace_mode),
10606            (
10607                LspInsertMode::ReplaceSubsequence,
10608                run.expected_with_replace_subsequence_mode,
10609            ),
10610            (
10611                LspInsertMode::ReplaceSuffix,
10612                run.expected_with_replace_suffix_mode,
10613            ),
10614        ];
10615
10616        for (lsp_insert_mode, expected_text) in run_variations {
10617            eprintln!(
10618                "run = {:?}, mode = {lsp_insert_mode:.?}",
10619                run.run_description,
10620            );
10621
10622            update_test_language_settings(&mut cx, |settings| {
10623                settings.defaults.completions = Some(CompletionSettings {
10624                    lsp_insert_mode,
10625                    words: WordsCompletionMode::Disabled,
10626                    lsp: true,
10627                    lsp_fetch_timeout_ms: 0,
10628                });
10629            });
10630
10631            cx.set_state(&run.initial_state);
10632            cx.update_editor(|editor, window, cx| {
10633                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10634            });
10635
10636            let counter = Arc::new(AtomicUsize::new(0));
10637            handle_completion_request_with_insert_and_replace(
10638                &mut cx,
10639                &run.buffer_marked_text,
10640                vec![run.completion_text],
10641                counter.clone(),
10642            )
10643            .await;
10644            cx.condition(|editor, _| editor.context_menu_visible())
10645                .await;
10646            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10647
10648            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10649                editor
10650                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10651                    .unwrap()
10652            });
10653            cx.assert_editor_state(&expected_text);
10654            handle_resolve_completion_request(&mut cx, None).await;
10655            apply_additional_edits.await.unwrap();
10656        }
10657    }
10658}
10659
10660#[gpui::test]
10661async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10662    init_test(cx, |_| {});
10663    let mut cx = EditorLspTestContext::new_rust(
10664        lsp::ServerCapabilities {
10665            completion_provider: Some(lsp::CompletionOptions {
10666                resolve_provider: Some(true),
10667                ..Default::default()
10668            }),
10669            ..Default::default()
10670        },
10671        cx,
10672    )
10673    .await;
10674
10675    let initial_state = "SubˇError";
10676    let buffer_marked_text = "<Sub|Error>";
10677    let completion_text = "SubscriptionError";
10678    let expected_with_insert_mode = "SubscriptionErrorˇError";
10679    let expected_with_replace_mode = "SubscriptionErrorˇ";
10680
10681    update_test_language_settings(&mut cx, |settings| {
10682        settings.defaults.completions = Some(CompletionSettings {
10683            words: WordsCompletionMode::Disabled,
10684            // set the opposite here to ensure that the action is overriding the default behavior
10685            lsp_insert_mode: LspInsertMode::Insert,
10686            lsp: true,
10687            lsp_fetch_timeout_ms: 0,
10688        });
10689    });
10690
10691    cx.set_state(initial_state);
10692    cx.update_editor(|editor, window, cx| {
10693        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10694    });
10695
10696    let counter = Arc::new(AtomicUsize::new(0));
10697    handle_completion_request_with_insert_and_replace(
10698        &mut cx,
10699        &buffer_marked_text,
10700        vec![completion_text],
10701        counter.clone(),
10702    )
10703    .await;
10704    cx.condition(|editor, _| editor.context_menu_visible())
10705        .await;
10706    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10707
10708    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10709        editor
10710            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10711            .unwrap()
10712    });
10713    cx.assert_editor_state(&expected_with_replace_mode);
10714    handle_resolve_completion_request(&mut cx, None).await;
10715    apply_additional_edits.await.unwrap();
10716
10717    update_test_language_settings(&mut cx, |settings| {
10718        settings.defaults.completions = Some(CompletionSettings {
10719            words: WordsCompletionMode::Disabled,
10720            // set the opposite here to ensure that the action is overriding the default behavior
10721            lsp_insert_mode: LspInsertMode::Replace,
10722            lsp: true,
10723            lsp_fetch_timeout_ms: 0,
10724        });
10725    });
10726
10727    cx.set_state(initial_state);
10728    cx.update_editor(|editor, window, cx| {
10729        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10730    });
10731    handle_completion_request_with_insert_and_replace(
10732        &mut cx,
10733        &buffer_marked_text,
10734        vec![completion_text],
10735        counter.clone(),
10736    )
10737    .await;
10738    cx.condition(|editor, _| editor.context_menu_visible())
10739        .await;
10740    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10741
10742    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10743        editor
10744            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10745            .unwrap()
10746    });
10747    cx.assert_editor_state(&expected_with_insert_mode);
10748    handle_resolve_completion_request(&mut cx, None).await;
10749    apply_additional_edits.await.unwrap();
10750}
10751
10752#[gpui::test]
10753async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10754    init_test(cx, |_| {});
10755    let mut cx = EditorLspTestContext::new_rust(
10756        lsp::ServerCapabilities {
10757            completion_provider: Some(lsp::CompletionOptions {
10758                resolve_provider: Some(true),
10759                ..Default::default()
10760            }),
10761            ..Default::default()
10762        },
10763        cx,
10764    )
10765    .await;
10766
10767    // scenario: surrounding text matches completion text
10768    let completion_text = "to_offset";
10769    let initial_state = indoc! {"
10770        1. buf.to_offˇsuffix
10771        2. buf.to_offˇsuf
10772        3. buf.to_offˇfix
10773        4. buf.to_offˇ
10774        5. into_offˇensive
10775        6. ˇsuffix
10776        7. let ˇ //
10777        8. aaˇzz
10778        9. buf.to_off«zzzzzˇ»suffix
10779        10. buf.«ˇzzzzz»suffix
10780        11. to_off«ˇzzzzz»
10781
10782        buf.to_offˇsuffix  // newest cursor
10783    "};
10784    let completion_marked_buffer = indoc! {"
10785        1. buf.to_offsuffix
10786        2. buf.to_offsuf
10787        3. buf.to_offfix
10788        4. buf.to_off
10789        5. into_offensive
10790        6. suffix
10791        7. let  //
10792        8. aazz
10793        9. buf.to_offzzzzzsuffix
10794        10. buf.zzzzzsuffix
10795        11. to_offzzzzz
10796
10797        buf.<to_off|suffix>  // newest cursor
10798    "};
10799    let expected = indoc! {"
10800        1. buf.to_offsetˇ
10801        2. buf.to_offsetˇsuf
10802        3. buf.to_offsetˇfix
10803        4. buf.to_offsetˇ
10804        5. into_offsetˇensive
10805        6. to_offsetˇsuffix
10806        7. let to_offsetˇ //
10807        8. aato_offsetˇzz
10808        9. buf.to_offsetˇ
10809        10. buf.to_offsetˇsuffix
10810        11. to_offsetˇ
10811
10812        buf.to_offsetˇ  // newest cursor
10813    "};
10814    cx.set_state(initial_state);
10815    cx.update_editor(|editor, window, cx| {
10816        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10817    });
10818    handle_completion_request_with_insert_and_replace(
10819        &mut cx,
10820        completion_marked_buffer,
10821        vec![completion_text],
10822        Arc::new(AtomicUsize::new(0)),
10823    )
10824    .await;
10825    cx.condition(|editor, _| editor.context_menu_visible())
10826        .await;
10827    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10828        editor
10829            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10830            .unwrap()
10831    });
10832    cx.assert_editor_state(expected);
10833    handle_resolve_completion_request(&mut cx, None).await;
10834    apply_additional_edits.await.unwrap();
10835
10836    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10837    let completion_text = "foo_and_bar";
10838    let initial_state = indoc! {"
10839        1. ooanbˇ
10840        2. zooanbˇ
10841        3. ooanbˇz
10842        4. zooanbˇz
10843        5. ooanˇ
10844        6. oanbˇ
10845
10846        ooanbˇ
10847    "};
10848    let completion_marked_buffer = indoc! {"
10849        1. ooanb
10850        2. zooanb
10851        3. ooanbz
10852        4. zooanbz
10853        5. ooan
10854        6. oanb
10855
10856        <ooanb|>
10857    "};
10858    let expected = indoc! {"
10859        1. foo_and_barˇ
10860        2. zfoo_and_barˇ
10861        3. foo_and_barˇz
10862        4. zfoo_and_barˇz
10863        5. ooanfoo_and_barˇ
10864        6. oanbfoo_and_barˇ
10865
10866        foo_and_barˇ
10867    "};
10868    cx.set_state(initial_state);
10869    cx.update_editor(|editor, window, cx| {
10870        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10871    });
10872    handle_completion_request_with_insert_and_replace(
10873        &mut cx,
10874        completion_marked_buffer,
10875        vec![completion_text],
10876        Arc::new(AtomicUsize::new(0)),
10877    )
10878    .await;
10879    cx.condition(|editor, _| editor.context_menu_visible())
10880        .await;
10881    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10882        editor
10883            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10884            .unwrap()
10885    });
10886    cx.assert_editor_state(expected);
10887    handle_resolve_completion_request(&mut cx, None).await;
10888    apply_additional_edits.await.unwrap();
10889
10890    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10891    // (expects the same as if it was inserted at the end)
10892    let completion_text = "foo_and_bar";
10893    let initial_state = indoc! {"
10894        1. ooˇanb
10895        2. zooˇanb
10896        3. ooˇanbz
10897        4. zooˇanbz
10898
10899        ooˇanb
10900    "};
10901    let completion_marked_buffer = indoc! {"
10902        1. ooanb
10903        2. zooanb
10904        3. ooanbz
10905        4. zooanbz
10906
10907        <oo|anb>
10908    "};
10909    let expected = indoc! {"
10910        1. foo_and_barˇ
10911        2. zfoo_and_barˇ
10912        3. foo_and_barˇz
10913        4. zfoo_and_barˇz
10914
10915        foo_and_barˇ
10916    "};
10917    cx.set_state(initial_state);
10918    cx.update_editor(|editor, window, cx| {
10919        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10920    });
10921    handle_completion_request_with_insert_and_replace(
10922        &mut cx,
10923        completion_marked_buffer,
10924        vec![completion_text],
10925        Arc::new(AtomicUsize::new(0)),
10926    )
10927    .await;
10928    cx.condition(|editor, _| editor.context_menu_visible())
10929        .await;
10930    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10931        editor
10932            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10933            .unwrap()
10934    });
10935    cx.assert_editor_state(expected);
10936    handle_resolve_completion_request(&mut cx, None).await;
10937    apply_additional_edits.await.unwrap();
10938}
10939
10940// This used to crash
10941#[gpui::test]
10942async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10943    init_test(cx, |_| {});
10944
10945    let buffer_text = indoc! {"
10946        fn main() {
10947            10.satu;
10948
10949            //
10950            // separate cursors so they open in different excerpts (manually reproducible)
10951            //
10952
10953            10.satu20;
10954        }
10955    "};
10956    let multibuffer_text_with_selections = indoc! {"
10957        fn main() {
10958            10.satuˇ;
10959
10960            //
10961
10962            //
10963
10964            10.satuˇ20;
10965        }
10966    "};
10967    let expected_multibuffer = indoc! {"
10968        fn main() {
10969            10.saturating_sub()ˇ;
10970
10971            //
10972
10973            //
10974
10975            10.saturating_sub()ˇ;
10976        }
10977    "};
10978
10979    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10980    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10981
10982    let fs = FakeFs::new(cx.executor());
10983    fs.insert_tree(
10984        path!("/a"),
10985        json!({
10986            "main.rs": buffer_text,
10987        }),
10988    )
10989    .await;
10990
10991    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10992    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10993    language_registry.add(rust_lang());
10994    let mut fake_servers = language_registry.register_fake_lsp(
10995        "Rust",
10996        FakeLspAdapter {
10997            capabilities: lsp::ServerCapabilities {
10998                completion_provider: Some(lsp::CompletionOptions {
10999                    resolve_provider: None,
11000                    ..lsp::CompletionOptions::default()
11001                }),
11002                ..lsp::ServerCapabilities::default()
11003            },
11004            ..FakeLspAdapter::default()
11005        },
11006    );
11007    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11008    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11009    let buffer = project
11010        .update(cx, |project, cx| {
11011            project.open_local_buffer(path!("/a/main.rs"), cx)
11012        })
11013        .await
11014        .unwrap();
11015
11016    let multi_buffer = cx.new(|cx| {
11017        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11018        multi_buffer.push_excerpts(
11019            buffer.clone(),
11020            [ExcerptRange::new(0..first_excerpt_end)],
11021            cx,
11022        );
11023        multi_buffer.push_excerpts(
11024            buffer.clone(),
11025            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11026            cx,
11027        );
11028        multi_buffer
11029    });
11030
11031    let editor = workspace
11032        .update(cx, |_, window, cx| {
11033            cx.new(|cx| {
11034                Editor::new(
11035                    EditorMode::Full {
11036                        scale_ui_elements_with_buffer_font_size: false,
11037                        show_active_line_background: false,
11038                        sized_by_content: false,
11039                    },
11040                    multi_buffer.clone(),
11041                    Some(project.clone()),
11042                    window,
11043                    cx,
11044                )
11045            })
11046        })
11047        .unwrap();
11048
11049    let pane = workspace
11050        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11051        .unwrap();
11052    pane.update_in(cx, |pane, window, cx| {
11053        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11054    });
11055
11056    let fake_server = fake_servers.next().await.unwrap();
11057
11058    editor.update_in(cx, |editor, window, cx| {
11059        editor.change_selections(None, window, cx, |s| {
11060            s.select_ranges([
11061                Point::new(1, 11)..Point::new(1, 11),
11062                Point::new(7, 11)..Point::new(7, 11),
11063            ])
11064        });
11065
11066        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11067    });
11068
11069    editor.update_in(cx, |editor, window, cx| {
11070        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11071    });
11072
11073    fake_server
11074        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11075            let completion_item = lsp::CompletionItem {
11076                label: "saturating_sub()".into(),
11077                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11078                    lsp::InsertReplaceEdit {
11079                        new_text: "saturating_sub()".to_owned(),
11080                        insert: lsp::Range::new(
11081                            lsp::Position::new(7, 7),
11082                            lsp::Position::new(7, 11),
11083                        ),
11084                        replace: lsp::Range::new(
11085                            lsp::Position::new(7, 7),
11086                            lsp::Position::new(7, 13),
11087                        ),
11088                    },
11089                )),
11090                ..lsp::CompletionItem::default()
11091            };
11092
11093            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11094        })
11095        .next()
11096        .await
11097        .unwrap();
11098
11099    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11100        .await;
11101
11102    editor
11103        .update_in(cx, |editor, window, cx| {
11104            editor
11105                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11106                .unwrap()
11107        })
11108        .await
11109        .unwrap();
11110
11111    editor.update(cx, |editor, cx| {
11112        assert_text_with_selections(editor, expected_multibuffer, cx);
11113    })
11114}
11115
11116#[gpui::test]
11117async fn test_completion(cx: &mut TestAppContext) {
11118    init_test(cx, |_| {});
11119
11120    let mut cx = EditorLspTestContext::new_rust(
11121        lsp::ServerCapabilities {
11122            completion_provider: Some(lsp::CompletionOptions {
11123                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11124                resolve_provider: Some(true),
11125                ..Default::default()
11126            }),
11127            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11128            ..Default::default()
11129        },
11130        cx,
11131    )
11132    .await;
11133    let counter = Arc::new(AtomicUsize::new(0));
11134
11135    cx.set_state(indoc! {"
11136        oneˇ
11137        two
11138        three
11139    "});
11140    cx.simulate_keystroke(".");
11141    handle_completion_request(
11142        &mut cx,
11143        indoc! {"
11144            one.|<>
11145            two
11146            three
11147        "},
11148        vec!["first_completion", "second_completion"],
11149        counter.clone(),
11150    )
11151    .await;
11152    cx.condition(|editor, _| editor.context_menu_visible())
11153        .await;
11154    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11155
11156    let _handler = handle_signature_help_request(
11157        &mut cx,
11158        lsp::SignatureHelp {
11159            signatures: vec![lsp::SignatureInformation {
11160                label: "test signature".to_string(),
11161                documentation: None,
11162                parameters: Some(vec![lsp::ParameterInformation {
11163                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11164                    documentation: None,
11165                }]),
11166                active_parameter: None,
11167            }],
11168            active_signature: None,
11169            active_parameter: None,
11170        },
11171    );
11172    cx.update_editor(|editor, window, cx| {
11173        assert!(
11174            !editor.signature_help_state.is_shown(),
11175            "No signature help was called for"
11176        );
11177        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11178    });
11179    cx.run_until_parked();
11180    cx.update_editor(|editor, _, _| {
11181        assert!(
11182            !editor.signature_help_state.is_shown(),
11183            "No signature help should be shown when completions menu is open"
11184        );
11185    });
11186
11187    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11188        editor.context_menu_next(&Default::default(), window, cx);
11189        editor
11190            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11191            .unwrap()
11192    });
11193    cx.assert_editor_state(indoc! {"
11194        one.second_completionˇ
11195        two
11196        three
11197    "});
11198
11199    handle_resolve_completion_request(
11200        &mut cx,
11201        Some(vec![
11202            (
11203                //This overlaps with the primary completion edit which is
11204                //misbehavior from the LSP spec, test that we filter it out
11205                indoc! {"
11206                    one.second_ˇcompletion
11207                    two
11208                    threeˇ
11209                "},
11210                "overlapping additional edit",
11211            ),
11212            (
11213                indoc! {"
11214                    one.second_completion
11215                    two
11216                    threeˇ
11217                "},
11218                "\nadditional edit",
11219            ),
11220        ]),
11221    )
11222    .await;
11223    apply_additional_edits.await.unwrap();
11224    cx.assert_editor_state(indoc! {"
11225        one.second_completionˇ
11226        two
11227        three
11228        additional edit
11229    "});
11230
11231    cx.set_state(indoc! {"
11232        one.second_completion
11233        twoˇ
11234        threeˇ
11235        additional edit
11236    "});
11237    cx.simulate_keystroke(" ");
11238    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11239    cx.simulate_keystroke("s");
11240    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11241
11242    cx.assert_editor_state(indoc! {"
11243        one.second_completion
11244        two sˇ
11245        three sˇ
11246        additional edit
11247    "});
11248    handle_completion_request(
11249        &mut cx,
11250        indoc! {"
11251            one.second_completion
11252            two s
11253            three <s|>
11254            additional edit
11255        "},
11256        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11257        counter.clone(),
11258    )
11259    .await;
11260    cx.condition(|editor, _| editor.context_menu_visible())
11261        .await;
11262    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11263
11264    cx.simulate_keystroke("i");
11265
11266    handle_completion_request(
11267        &mut cx,
11268        indoc! {"
11269            one.second_completion
11270            two si
11271            three <si|>
11272            additional edit
11273        "},
11274        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11275        counter.clone(),
11276    )
11277    .await;
11278    cx.condition(|editor, _| editor.context_menu_visible())
11279        .await;
11280    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11281
11282    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11283        editor
11284            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11285            .unwrap()
11286    });
11287    cx.assert_editor_state(indoc! {"
11288        one.second_completion
11289        two sixth_completionˇ
11290        three sixth_completionˇ
11291        additional edit
11292    "});
11293
11294    apply_additional_edits.await.unwrap();
11295
11296    update_test_language_settings(&mut cx, |settings| {
11297        settings.defaults.show_completions_on_input = Some(false);
11298    });
11299    cx.set_state("editorˇ");
11300    cx.simulate_keystroke(".");
11301    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11302    cx.simulate_keystrokes("c l o");
11303    cx.assert_editor_state("editor.cloˇ");
11304    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11305    cx.update_editor(|editor, window, cx| {
11306        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11307    });
11308    handle_completion_request(
11309        &mut cx,
11310        "editor.<clo|>",
11311        vec!["close", "clobber"],
11312        counter.clone(),
11313    )
11314    .await;
11315    cx.condition(|editor, _| editor.context_menu_visible())
11316        .await;
11317    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11318
11319    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11320        editor
11321            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11322            .unwrap()
11323    });
11324    cx.assert_editor_state("editor.closeˇ");
11325    handle_resolve_completion_request(&mut cx, None).await;
11326    apply_additional_edits.await.unwrap();
11327}
11328
11329#[gpui::test]
11330async fn test_word_completion(cx: &mut TestAppContext) {
11331    let lsp_fetch_timeout_ms = 10;
11332    init_test(cx, |language_settings| {
11333        language_settings.defaults.completions = Some(CompletionSettings {
11334            words: WordsCompletionMode::Fallback,
11335            lsp: true,
11336            lsp_fetch_timeout_ms: 10,
11337            lsp_insert_mode: LspInsertMode::Insert,
11338        });
11339    });
11340
11341    let mut cx = EditorLspTestContext::new_rust(
11342        lsp::ServerCapabilities {
11343            completion_provider: Some(lsp::CompletionOptions {
11344                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11345                ..lsp::CompletionOptions::default()
11346            }),
11347            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11348            ..lsp::ServerCapabilities::default()
11349        },
11350        cx,
11351    )
11352    .await;
11353
11354    let throttle_completions = Arc::new(AtomicBool::new(false));
11355
11356    let lsp_throttle_completions = throttle_completions.clone();
11357    let _completion_requests_handler =
11358        cx.lsp
11359            .server
11360            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11361                let lsp_throttle_completions = lsp_throttle_completions.clone();
11362                let cx = cx.clone();
11363                async move {
11364                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11365                        cx.background_executor()
11366                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11367                            .await;
11368                    }
11369                    Ok(Some(lsp::CompletionResponse::Array(vec![
11370                        lsp::CompletionItem {
11371                            label: "first".into(),
11372                            ..lsp::CompletionItem::default()
11373                        },
11374                        lsp::CompletionItem {
11375                            label: "last".into(),
11376                            ..lsp::CompletionItem::default()
11377                        },
11378                    ])))
11379                }
11380            });
11381
11382    cx.set_state(indoc! {"
11383        oneˇ
11384        two
11385        three
11386    "});
11387    cx.simulate_keystroke(".");
11388    cx.executor().run_until_parked();
11389    cx.condition(|editor, _| editor.context_menu_visible())
11390        .await;
11391    cx.update_editor(|editor, window, cx| {
11392        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11393        {
11394            assert_eq!(
11395                completion_menu_entries(&menu),
11396                &["first", "last"],
11397                "When LSP server is fast to reply, no fallback word completions are used"
11398            );
11399        } else {
11400            panic!("expected completion menu to be open");
11401        }
11402        editor.cancel(&Cancel, window, cx);
11403    });
11404    cx.executor().run_until_parked();
11405    cx.condition(|editor, _| !editor.context_menu_visible())
11406        .await;
11407
11408    throttle_completions.store(true, atomic::Ordering::Release);
11409    cx.simulate_keystroke(".");
11410    cx.executor()
11411        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
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), &["one", "three", "two"],
11419                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11420        } else {
11421            panic!("expected completion menu to be open");
11422        }
11423    });
11424}
11425
11426#[gpui::test]
11427async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11428    init_test(cx, |language_settings| {
11429        language_settings.defaults.completions = Some(CompletionSettings {
11430            words: WordsCompletionMode::Enabled,
11431            lsp: true,
11432            lsp_fetch_timeout_ms: 0,
11433            lsp_insert_mode: LspInsertMode::Insert,
11434        });
11435    });
11436
11437    let mut cx = EditorLspTestContext::new_rust(
11438        lsp::ServerCapabilities {
11439            completion_provider: Some(lsp::CompletionOptions {
11440                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11441                ..lsp::CompletionOptions::default()
11442            }),
11443            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11444            ..lsp::ServerCapabilities::default()
11445        },
11446        cx,
11447    )
11448    .await;
11449
11450    let _completion_requests_handler =
11451        cx.lsp
11452            .server
11453            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11454                Ok(Some(lsp::CompletionResponse::Array(vec![
11455                    lsp::CompletionItem {
11456                        label: "first".into(),
11457                        ..lsp::CompletionItem::default()
11458                    },
11459                    lsp::CompletionItem {
11460                        label: "last".into(),
11461                        ..lsp::CompletionItem::default()
11462                    },
11463                ])))
11464            });
11465
11466    cx.set_state(indoc! {"ˇ
11467        first
11468        last
11469        second
11470    "});
11471    cx.simulate_keystroke(".");
11472    cx.executor().run_until_parked();
11473    cx.condition(|editor, _| editor.context_menu_visible())
11474        .await;
11475    cx.update_editor(|editor, _, _| {
11476        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11477        {
11478            assert_eq!(
11479                completion_menu_entries(&menu),
11480                &["first", "last", "second"],
11481                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11482            );
11483        } else {
11484            panic!("expected completion menu to be open");
11485        }
11486    });
11487}
11488
11489#[gpui::test]
11490async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11491    init_test(cx, |language_settings| {
11492        language_settings.defaults.completions = Some(CompletionSettings {
11493            words: WordsCompletionMode::Disabled,
11494            lsp: true,
11495            lsp_fetch_timeout_ms: 0,
11496            lsp_insert_mode: LspInsertMode::Insert,
11497        });
11498    });
11499
11500    let mut cx = EditorLspTestContext::new_rust(
11501        lsp::ServerCapabilities {
11502            completion_provider: Some(lsp::CompletionOptions {
11503                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11504                ..lsp::CompletionOptions::default()
11505            }),
11506            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11507            ..lsp::ServerCapabilities::default()
11508        },
11509        cx,
11510    )
11511    .await;
11512
11513    let _completion_requests_handler =
11514        cx.lsp
11515            .server
11516            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11517                panic!("LSP completions should not be queried when dealing with word completions")
11518            });
11519
11520    cx.set_state(indoc! {"ˇ
11521        first
11522        last
11523        second
11524    "});
11525    cx.update_editor(|editor, window, cx| {
11526        editor.show_word_completions(&ShowWordCompletions, window, cx);
11527    });
11528    cx.executor().run_until_parked();
11529    cx.condition(|editor, _| editor.context_menu_visible())
11530        .await;
11531    cx.update_editor(|editor, _, _| {
11532        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11533        {
11534            assert_eq!(
11535                completion_menu_entries(&menu),
11536                &["first", "last", "second"],
11537                "`ShowWordCompletions` action should show word completions"
11538            );
11539        } else {
11540            panic!("expected completion menu to be open");
11541        }
11542    });
11543
11544    cx.simulate_keystroke("l");
11545    cx.executor().run_until_parked();
11546    cx.condition(|editor, _| editor.context_menu_visible())
11547        .await;
11548    cx.update_editor(|editor, _, _| {
11549        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11550        {
11551            assert_eq!(
11552                completion_menu_entries(&menu),
11553                &["last"],
11554                "After showing word completions, further editing should filter them and not query the LSP"
11555            );
11556        } else {
11557            panic!("expected completion menu to be open");
11558        }
11559    });
11560}
11561
11562#[gpui::test]
11563async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11564    init_test(cx, |language_settings| {
11565        language_settings.defaults.completions = Some(CompletionSettings {
11566            words: WordsCompletionMode::Fallback,
11567            lsp: false,
11568            lsp_fetch_timeout_ms: 0,
11569            lsp_insert_mode: LspInsertMode::Insert,
11570        });
11571    });
11572
11573    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11574
11575    cx.set_state(indoc! {"ˇ
11576        0_usize
11577        let
11578        33
11579        4.5f32
11580    "});
11581    cx.update_editor(|editor, window, cx| {
11582        editor.show_completions(&ShowCompletions::default(), window, cx);
11583    });
11584    cx.executor().run_until_parked();
11585    cx.condition(|editor, _| editor.context_menu_visible())
11586        .await;
11587    cx.update_editor(|editor, window, cx| {
11588        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11589        {
11590            assert_eq!(
11591                completion_menu_entries(&menu),
11592                &["let"],
11593                "With no digits in the completion query, no digits should be in the word completions"
11594            );
11595        } else {
11596            panic!("expected completion menu to be open");
11597        }
11598        editor.cancel(&Cancel, window, cx);
11599    });
11600
11601    cx.set_state(indoc! {"11602        0_usize
11603        let
11604        3
11605        33.35f32
11606    "});
11607    cx.update_editor(|editor, window, cx| {
11608        editor.show_completions(&ShowCompletions::default(), window, cx);
11609    });
11610    cx.executor().run_until_parked();
11611    cx.condition(|editor, _| editor.context_menu_visible())
11612        .await;
11613    cx.update_editor(|editor, _, _| {
11614        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11615        {
11616            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11617                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11618        } else {
11619            panic!("expected completion menu to be open");
11620        }
11621    });
11622}
11623
11624fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11625    let position = || lsp::Position {
11626        line: params.text_document_position.position.line,
11627        character: params.text_document_position.position.character,
11628    };
11629    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11630        range: lsp::Range {
11631            start: position(),
11632            end: position(),
11633        },
11634        new_text: text.to_string(),
11635    }))
11636}
11637
11638#[gpui::test]
11639async fn test_multiline_completion(cx: &mut TestAppContext) {
11640    init_test(cx, |_| {});
11641
11642    let fs = FakeFs::new(cx.executor());
11643    fs.insert_tree(
11644        path!("/a"),
11645        json!({
11646            "main.ts": "a",
11647        }),
11648    )
11649    .await;
11650
11651    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11652    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11653    let typescript_language = Arc::new(Language::new(
11654        LanguageConfig {
11655            name: "TypeScript".into(),
11656            matcher: LanguageMatcher {
11657                path_suffixes: vec!["ts".to_string()],
11658                ..LanguageMatcher::default()
11659            },
11660            line_comments: vec!["// ".into()],
11661            ..LanguageConfig::default()
11662        },
11663        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11664    ));
11665    language_registry.add(typescript_language.clone());
11666    let mut fake_servers = language_registry.register_fake_lsp(
11667        "TypeScript",
11668        FakeLspAdapter {
11669            capabilities: lsp::ServerCapabilities {
11670                completion_provider: Some(lsp::CompletionOptions {
11671                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11672                    ..lsp::CompletionOptions::default()
11673                }),
11674                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11675                ..lsp::ServerCapabilities::default()
11676            },
11677            // Emulate vtsls label generation
11678            label_for_completion: Some(Box::new(|item, _| {
11679                let text = if let Some(description) = item
11680                    .label_details
11681                    .as_ref()
11682                    .and_then(|label_details| label_details.description.as_ref())
11683                {
11684                    format!("{} {}", item.label, description)
11685                } else if let Some(detail) = &item.detail {
11686                    format!("{} {}", item.label, detail)
11687                } else {
11688                    item.label.clone()
11689                };
11690                let len = text.len();
11691                Some(language::CodeLabel {
11692                    text,
11693                    runs: Vec::new(),
11694                    filter_range: 0..len,
11695                })
11696            })),
11697            ..FakeLspAdapter::default()
11698        },
11699    );
11700    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11701    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11702    let worktree_id = workspace
11703        .update(cx, |workspace, _window, cx| {
11704            workspace.project().update(cx, |project, cx| {
11705                project.worktrees(cx).next().unwrap().read(cx).id()
11706            })
11707        })
11708        .unwrap();
11709    let _buffer = project
11710        .update(cx, |project, cx| {
11711            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11712        })
11713        .await
11714        .unwrap();
11715    let editor = workspace
11716        .update(cx, |workspace, window, cx| {
11717            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11718        })
11719        .unwrap()
11720        .await
11721        .unwrap()
11722        .downcast::<Editor>()
11723        .unwrap();
11724    let fake_server = fake_servers.next().await.unwrap();
11725
11726    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11727    let multiline_label_2 = "a\nb\nc\n";
11728    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11729    let multiline_description = "d\ne\nf\n";
11730    let multiline_detail_2 = "g\nh\ni\n";
11731
11732    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11733        move |params, _| async move {
11734            Ok(Some(lsp::CompletionResponse::Array(vec![
11735                lsp::CompletionItem {
11736                    label: multiline_label.to_string(),
11737                    text_edit: gen_text_edit(&params, "new_text_1"),
11738                    ..lsp::CompletionItem::default()
11739                },
11740                lsp::CompletionItem {
11741                    label: "single line label 1".to_string(),
11742                    detail: Some(multiline_detail.to_string()),
11743                    text_edit: gen_text_edit(&params, "new_text_2"),
11744                    ..lsp::CompletionItem::default()
11745                },
11746                lsp::CompletionItem {
11747                    label: "single line label 2".to_string(),
11748                    label_details: Some(lsp::CompletionItemLabelDetails {
11749                        description: Some(multiline_description.to_string()),
11750                        detail: None,
11751                    }),
11752                    text_edit: gen_text_edit(&params, "new_text_2"),
11753                    ..lsp::CompletionItem::default()
11754                },
11755                lsp::CompletionItem {
11756                    label: multiline_label_2.to_string(),
11757                    detail: Some(multiline_detail_2.to_string()),
11758                    text_edit: gen_text_edit(&params, "new_text_3"),
11759                    ..lsp::CompletionItem::default()
11760                },
11761                lsp::CompletionItem {
11762                    label: "Label with many     spaces and \t but without newlines".to_string(),
11763                    detail: Some(
11764                        "Details with many     spaces and \t but without newlines".to_string(),
11765                    ),
11766                    text_edit: gen_text_edit(&params, "new_text_4"),
11767                    ..lsp::CompletionItem::default()
11768                },
11769            ])))
11770        },
11771    );
11772
11773    editor.update_in(cx, |editor, window, cx| {
11774        cx.focus_self(window);
11775        editor.move_to_end(&MoveToEnd, window, cx);
11776        editor.handle_input(".", window, cx);
11777    });
11778    cx.run_until_parked();
11779    completion_handle.next().await.unwrap();
11780
11781    editor.update(cx, |editor, _| {
11782        assert!(editor.context_menu_visible());
11783        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11784        {
11785            let completion_labels = menu
11786                .completions
11787                .borrow()
11788                .iter()
11789                .map(|c| c.label.text.clone())
11790                .collect::<Vec<_>>();
11791            assert_eq!(
11792                completion_labels,
11793                &[
11794                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11795                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11796                    "single line label 2 d e f ",
11797                    "a b c g h i ",
11798                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11799                ],
11800                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11801            );
11802
11803            for completion in menu
11804                .completions
11805                .borrow()
11806                .iter() {
11807                    assert_eq!(
11808                        completion.label.filter_range,
11809                        0..completion.label.text.len(),
11810                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11811                    );
11812                }
11813        } else {
11814            panic!("expected completion menu to be open");
11815        }
11816    });
11817}
11818
11819#[gpui::test]
11820async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11821    init_test(cx, |_| {});
11822    let mut cx = EditorLspTestContext::new_rust(
11823        lsp::ServerCapabilities {
11824            completion_provider: Some(lsp::CompletionOptions {
11825                trigger_characters: Some(vec![".".to_string()]),
11826                ..Default::default()
11827            }),
11828            ..Default::default()
11829        },
11830        cx,
11831    )
11832    .await;
11833    cx.lsp
11834        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11835            Ok(Some(lsp::CompletionResponse::Array(vec![
11836                lsp::CompletionItem {
11837                    label: "first".into(),
11838                    ..Default::default()
11839                },
11840                lsp::CompletionItem {
11841                    label: "last".into(),
11842                    ..Default::default()
11843                },
11844            ])))
11845        });
11846    cx.set_state("variableˇ");
11847    cx.simulate_keystroke(".");
11848    cx.executor().run_until_parked();
11849
11850    cx.update_editor(|editor, _, _| {
11851        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11852        {
11853            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11854        } else {
11855            panic!("expected completion menu to be open");
11856        }
11857    });
11858
11859    cx.update_editor(|editor, window, cx| {
11860        editor.move_page_down(&MovePageDown::default(), window, cx);
11861        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11862        {
11863            assert!(
11864                menu.selected_item == 1,
11865                "expected PageDown to select the last item from the context menu"
11866            );
11867        } else {
11868            panic!("expected completion menu to stay open after PageDown");
11869        }
11870    });
11871
11872    cx.update_editor(|editor, window, cx| {
11873        editor.move_page_up(&MovePageUp::default(), window, cx);
11874        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11875        {
11876            assert!(
11877                menu.selected_item == 0,
11878                "expected PageUp to select the first item from the context menu"
11879            );
11880        } else {
11881            panic!("expected completion menu to stay open after PageUp");
11882        }
11883    });
11884}
11885
11886#[gpui::test]
11887async fn test_as_is_completions(cx: &mut TestAppContext) {
11888    init_test(cx, |_| {});
11889    let mut cx = EditorLspTestContext::new_rust(
11890        lsp::ServerCapabilities {
11891            completion_provider: Some(lsp::CompletionOptions {
11892                ..Default::default()
11893            }),
11894            ..Default::default()
11895        },
11896        cx,
11897    )
11898    .await;
11899    cx.lsp
11900        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11901            Ok(Some(lsp::CompletionResponse::Array(vec![
11902                lsp::CompletionItem {
11903                    label: "unsafe".into(),
11904                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11905                        range: lsp::Range {
11906                            start: lsp::Position {
11907                                line: 1,
11908                                character: 2,
11909                            },
11910                            end: lsp::Position {
11911                                line: 1,
11912                                character: 3,
11913                            },
11914                        },
11915                        new_text: "unsafe".to_string(),
11916                    })),
11917                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11918                    ..Default::default()
11919                },
11920            ])))
11921        });
11922    cx.set_state("fn a() {}\n");
11923    cx.executor().run_until_parked();
11924    cx.update_editor(|editor, window, cx| {
11925        editor.show_completions(
11926            &ShowCompletions {
11927                trigger: Some("\n".into()),
11928            },
11929            window,
11930            cx,
11931        );
11932    });
11933    cx.executor().run_until_parked();
11934
11935    cx.update_editor(|editor, window, cx| {
11936        editor.confirm_completion(&Default::default(), window, cx)
11937    });
11938    cx.executor().run_until_parked();
11939    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11940}
11941
11942#[gpui::test]
11943async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11944    init_test(cx, |_| {});
11945
11946    let mut cx = EditorLspTestContext::new_rust(
11947        lsp::ServerCapabilities {
11948            completion_provider: Some(lsp::CompletionOptions {
11949                trigger_characters: Some(vec![".".to_string()]),
11950                resolve_provider: Some(true),
11951                ..Default::default()
11952            }),
11953            ..Default::default()
11954        },
11955        cx,
11956    )
11957    .await;
11958
11959    cx.set_state("fn main() { let a = 2ˇ; }");
11960    cx.simulate_keystroke(".");
11961    let completion_item = lsp::CompletionItem {
11962        label: "Some".into(),
11963        kind: Some(lsp::CompletionItemKind::SNIPPET),
11964        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11965        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11966            kind: lsp::MarkupKind::Markdown,
11967            value: "```rust\nSome(2)\n```".to_string(),
11968        })),
11969        deprecated: Some(false),
11970        sort_text: Some("Some".to_string()),
11971        filter_text: Some("Some".to_string()),
11972        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11973        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11974            range: lsp::Range {
11975                start: lsp::Position {
11976                    line: 0,
11977                    character: 22,
11978                },
11979                end: lsp::Position {
11980                    line: 0,
11981                    character: 22,
11982                },
11983            },
11984            new_text: "Some(2)".to_string(),
11985        })),
11986        additional_text_edits: Some(vec![lsp::TextEdit {
11987            range: lsp::Range {
11988                start: lsp::Position {
11989                    line: 0,
11990                    character: 20,
11991                },
11992                end: lsp::Position {
11993                    line: 0,
11994                    character: 22,
11995                },
11996            },
11997            new_text: "".to_string(),
11998        }]),
11999        ..Default::default()
12000    };
12001
12002    let closure_completion_item = completion_item.clone();
12003    let counter = Arc::new(AtomicUsize::new(0));
12004    let counter_clone = counter.clone();
12005    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12006        let task_completion_item = closure_completion_item.clone();
12007        counter_clone.fetch_add(1, atomic::Ordering::Release);
12008        async move {
12009            Ok(Some(lsp::CompletionResponse::Array(vec![
12010                task_completion_item,
12011            ])))
12012        }
12013    });
12014
12015    cx.condition(|editor, _| editor.context_menu_visible())
12016        .await;
12017    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12018    assert!(request.next().await.is_some());
12019    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12020
12021    cx.simulate_keystrokes("S o m");
12022    cx.condition(|editor, _| editor.context_menu_visible())
12023        .await;
12024    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12025    assert!(request.next().await.is_some());
12026    assert!(request.next().await.is_some());
12027    assert!(request.next().await.is_some());
12028    request.close();
12029    assert!(request.next().await.is_none());
12030    assert_eq!(
12031        counter.load(atomic::Ordering::Acquire),
12032        4,
12033        "With the completions menu open, only one LSP request should happen per input"
12034    );
12035}
12036
12037#[gpui::test]
12038async fn test_toggle_comment(cx: &mut TestAppContext) {
12039    init_test(cx, |_| {});
12040    let mut cx = EditorTestContext::new(cx).await;
12041    let language = Arc::new(Language::new(
12042        LanguageConfig {
12043            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12044            ..Default::default()
12045        },
12046        Some(tree_sitter_rust::LANGUAGE.into()),
12047    ));
12048    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12049
12050    // If multiple selections intersect a line, the line is only toggled once.
12051    cx.set_state(indoc! {"
12052        fn a() {
12053            «//b();
12054            ˇ»// «c();
12055            //ˇ»  d();
12056        }
12057    "});
12058
12059    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12060
12061    cx.assert_editor_state(indoc! {"
12062        fn a() {
12063            «b();
12064            c();
12065            ˇ» d();
12066        }
12067    "});
12068
12069    // The comment prefix is inserted at the same column for every line in a
12070    // selection.
12071    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12072
12073    cx.assert_editor_state(indoc! {"
12074        fn a() {
12075            // «b();
12076            // c();
12077            ˇ»//  d();
12078        }
12079    "});
12080
12081    // If a selection ends at the beginning of a line, that line is not toggled.
12082    cx.set_selections_state(indoc! {"
12083        fn a() {
12084            // b();
12085            «// c();
12086        ˇ»    //  d();
12087        }
12088    "});
12089
12090    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12091
12092    cx.assert_editor_state(indoc! {"
12093        fn a() {
12094            // b();
12095            «c();
12096        ˇ»    //  d();
12097        }
12098    "});
12099
12100    // If a selection span a single line and is empty, the line is toggled.
12101    cx.set_state(indoc! {"
12102        fn a() {
12103            a();
12104            b();
12105        ˇ
12106        }
12107    "});
12108
12109    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12110
12111    cx.assert_editor_state(indoc! {"
12112        fn a() {
12113            a();
12114            b();
12115        //•ˇ
12116        }
12117    "});
12118
12119    // If a selection span multiple lines, empty lines are not toggled.
12120    cx.set_state(indoc! {"
12121        fn a() {
12122            «a();
12123
12124            c();ˇ»
12125        }
12126    "});
12127
12128    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12129
12130    cx.assert_editor_state(indoc! {"
12131        fn a() {
12132            // «a();
12133
12134            // c();ˇ»
12135        }
12136    "});
12137
12138    // If a selection includes multiple comment prefixes, all lines are uncommented.
12139    cx.set_state(indoc! {"
12140        fn a() {
12141            «// a();
12142            /// b();
12143            //! c();ˇ»
12144        }
12145    "});
12146
12147    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12148
12149    cx.assert_editor_state(indoc! {"
12150        fn a() {
12151            «a();
12152            b();
12153            c();ˇ»
12154        }
12155    "});
12156}
12157
12158#[gpui::test]
12159async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12160    init_test(cx, |_| {});
12161    let mut cx = EditorTestContext::new(cx).await;
12162    let language = Arc::new(Language::new(
12163        LanguageConfig {
12164            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12165            ..Default::default()
12166        },
12167        Some(tree_sitter_rust::LANGUAGE.into()),
12168    ));
12169    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12170
12171    let toggle_comments = &ToggleComments {
12172        advance_downwards: false,
12173        ignore_indent: true,
12174    };
12175
12176    // If multiple selections intersect a line, the line is only toggled once.
12177    cx.set_state(indoc! {"
12178        fn a() {
12179        //    «b();
12180        //    c();
12181        //    ˇ» d();
12182        }
12183    "});
12184
12185    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12186
12187    cx.assert_editor_state(indoc! {"
12188        fn a() {
12189            «b();
12190            c();
12191            ˇ» d();
12192        }
12193    "});
12194
12195    // The comment prefix is inserted at the beginning of each line
12196    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12197
12198    cx.assert_editor_state(indoc! {"
12199        fn a() {
12200        //    «b();
12201        //    c();
12202        //    ˇ» d();
12203        }
12204    "});
12205
12206    // If a selection ends at the beginning of a line, that line is not toggled.
12207    cx.set_selections_state(indoc! {"
12208        fn a() {
12209        //    b();
12210        //    «c();
12211        ˇ»//     d();
12212        }
12213    "});
12214
12215    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12216
12217    cx.assert_editor_state(indoc! {"
12218        fn a() {
12219        //    b();
12220            «c();
12221        ˇ»//     d();
12222        }
12223    "});
12224
12225    // If a selection span a single line and is empty, the line is toggled.
12226    cx.set_state(indoc! {"
12227        fn a() {
12228            a();
12229            b();
12230        ˇ
12231        }
12232    "});
12233
12234    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12235
12236    cx.assert_editor_state(indoc! {"
12237        fn a() {
12238            a();
12239            b();
12240        //ˇ
12241        }
12242    "});
12243
12244    // If a selection span multiple lines, empty lines are not toggled.
12245    cx.set_state(indoc! {"
12246        fn a() {
12247            «a();
12248
12249            c();ˇ»
12250        }
12251    "});
12252
12253    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12254
12255    cx.assert_editor_state(indoc! {"
12256        fn a() {
12257        //    «a();
12258
12259        //    c();ˇ»
12260        }
12261    "});
12262
12263    // If a selection includes multiple comment prefixes, all lines are uncommented.
12264    cx.set_state(indoc! {"
12265        fn a() {
12266        //    «a();
12267        ///    b();
12268        //!    c();ˇ»
12269        }
12270    "});
12271
12272    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12273
12274    cx.assert_editor_state(indoc! {"
12275        fn a() {
12276            «a();
12277            b();
12278            c();ˇ»
12279        }
12280    "});
12281}
12282
12283#[gpui::test]
12284async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12285    init_test(cx, |_| {});
12286
12287    let language = Arc::new(Language::new(
12288        LanguageConfig {
12289            line_comments: vec!["// ".into()],
12290            ..Default::default()
12291        },
12292        Some(tree_sitter_rust::LANGUAGE.into()),
12293    ));
12294
12295    let mut cx = EditorTestContext::new(cx).await;
12296
12297    cx.language_registry().add(language.clone());
12298    cx.update_buffer(|buffer, cx| {
12299        buffer.set_language(Some(language), cx);
12300    });
12301
12302    let toggle_comments = &ToggleComments {
12303        advance_downwards: true,
12304        ignore_indent: false,
12305    };
12306
12307    // Single cursor on one line -> advance
12308    // Cursor moves horizontally 3 characters as well on non-blank line
12309    cx.set_state(indoc!(
12310        "fn a() {
12311             ˇdog();
12312             cat();
12313        }"
12314    ));
12315    cx.update_editor(|editor, window, cx| {
12316        editor.toggle_comments(toggle_comments, window, cx);
12317    });
12318    cx.assert_editor_state(indoc!(
12319        "fn a() {
12320             // dog();
12321             catˇ();
12322        }"
12323    ));
12324
12325    // Single selection on one line -> don't advance
12326    cx.set_state(indoc!(
12327        "fn a() {
12328             «dog()ˇ»;
12329             cat();
12330        }"
12331    ));
12332    cx.update_editor(|editor, window, cx| {
12333        editor.toggle_comments(toggle_comments, window, cx);
12334    });
12335    cx.assert_editor_state(indoc!(
12336        "fn a() {
12337             // «dog()ˇ»;
12338             cat();
12339        }"
12340    ));
12341
12342    // Multiple cursors on one line -> advance
12343    cx.set_state(indoc!(
12344        "fn a() {
12345             ˇdˇog();
12346             cat();
12347        }"
12348    ));
12349    cx.update_editor(|editor, window, cx| {
12350        editor.toggle_comments(toggle_comments, window, cx);
12351    });
12352    cx.assert_editor_state(indoc!(
12353        "fn a() {
12354             // dog();
12355             catˇ(ˇ);
12356        }"
12357    ));
12358
12359    // Multiple cursors on one line, with selection -> don't advance
12360    cx.set_state(indoc!(
12361        "fn a() {
12362             ˇdˇog«()ˇ»;
12363             cat();
12364        }"
12365    ));
12366    cx.update_editor(|editor, window, cx| {
12367        editor.toggle_comments(toggle_comments, window, cx);
12368    });
12369    cx.assert_editor_state(indoc!(
12370        "fn a() {
12371             // ˇdˇog«()ˇ»;
12372             cat();
12373        }"
12374    ));
12375
12376    // Single cursor on one line -> advance
12377    // Cursor moves to column 0 on blank line
12378    cx.set_state(indoc!(
12379        "fn a() {
12380             ˇdog();
12381
12382             cat();
12383        }"
12384    ));
12385    cx.update_editor(|editor, window, cx| {
12386        editor.toggle_comments(toggle_comments, window, cx);
12387    });
12388    cx.assert_editor_state(indoc!(
12389        "fn a() {
12390             // dog();
12391        ˇ
12392             cat();
12393        }"
12394    ));
12395
12396    // Single cursor on one line -> advance
12397    // Cursor starts and ends at column 0
12398    cx.set_state(indoc!(
12399        "fn a() {
12400         ˇ    dog();
12401             cat();
12402        }"
12403    ));
12404    cx.update_editor(|editor, window, cx| {
12405        editor.toggle_comments(toggle_comments, window, cx);
12406    });
12407    cx.assert_editor_state(indoc!(
12408        "fn a() {
12409             // dog();
12410         ˇ    cat();
12411        }"
12412    ));
12413}
12414
12415#[gpui::test]
12416async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12417    init_test(cx, |_| {});
12418
12419    let mut cx = EditorTestContext::new(cx).await;
12420
12421    let html_language = Arc::new(
12422        Language::new(
12423            LanguageConfig {
12424                name: "HTML".into(),
12425                block_comment: Some(("<!-- ".into(), " -->".into())),
12426                ..Default::default()
12427            },
12428            Some(tree_sitter_html::LANGUAGE.into()),
12429        )
12430        .with_injection_query(
12431            r#"
12432            (script_element
12433                (raw_text) @injection.content
12434                (#set! injection.language "javascript"))
12435            "#,
12436        )
12437        .unwrap(),
12438    );
12439
12440    let javascript_language = Arc::new(Language::new(
12441        LanguageConfig {
12442            name: "JavaScript".into(),
12443            line_comments: vec!["// ".into()],
12444            ..Default::default()
12445        },
12446        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12447    ));
12448
12449    cx.language_registry().add(html_language.clone());
12450    cx.language_registry().add(javascript_language.clone());
12451    cx.update_buffer(|buffer, cx| {
12452        buffer.set_language(Some(html_language), cx);
12453    });
12454
12455    // Toggle comments for empty selections
12456    cx.set_state(
12457        &r#"
12458            <p>A</p>ˇ
12459            <p>B</p>ˇ
12460            <p>C</p>ˇ
12461        "#
12462        .unindent(),
12463    );
12464    cx.update_editor(|editor, window, cx| {
12465        editor.toggle_comments(&ToggleComments::default(), window, cx)
12466    });
12467    cx.assert_editor_state(
12468        &r#"
12469            <!-- <p>A</p>ˇ -->
12470            <!-- <p>B</p>ˇ -->
12471            <!-- <p>C</p>ˇ -->
12472        "#
12473        .unindent(),
12474    );
12475    cx.update_editor(|editor, window, cx| {
12476        editor.toggle_comments(&ToggleComments::default(), window, cx)
12477    });
12478    cx.assert_editor_state(
12479        &r#"
12480            <p>A</p>ˇ
12481            <p>B</p>ˇ
12482            <p>C</p>ˇ
12483        "#
12484        .unindent(),
12485    );
12486
12487    // Toggle comments for mixture of empty and non-empty selections, where
12488    // multiple selections occupy a given line.
12489    cx.set_state(
12490        &r#"
12491            <p>A«</p>
12492            <p>ˇ»B</p>ˇ
12493            <p>C«</p>
12494            <p>ˇ»D</p>ˇ
12495        "#
12496        .unindent(),
12497    );
12498
12499    cx.update_editor(|editor, window, cx| {
12500        editor.toggle_comments(&ToggleComments::default(), window, cx)
12501    });
12502    cx.assert_editor_state(
12503        &r#"
12504            <!-- <p>A«</p>
12505            <p>ˇ»B</p>ˇ -->
12506            <!-- <p>C«</p>
12507            <p>ˇ»D</p>ˇ -->
12508        "#
12509        .unindent(),
12510    );
12511    cx.update_editor(|editor, window, cx| {
12512        editor.toggle_comments(&ToggleComments::default(), window, cx)
12513    });
12514    cx.assert_editor_state(
12515        &r#"
12516            <p>A«</p>
12517            <p>ˇ»B</p>ˇ
12518            <p>C«</p>
12519            <p>ˇ»D</p>ˇ
12520        "#
12521        .unindent(),
12522    );
12523
12524    // Toggle comments when different languages are active for different
12525    // selections.
12526    cx.set_state(
12527        &r#"
12528            ˇ<script>
12529                ˇvar x = new Y();
12530            ˇ</script>
12531        "#
12532        .unindent(),
12533    );
12534    cx.executor().run_until_parked();
12535    cx.update_editor(|editor, window, cx| {
12536        editor.toggle_comments(&ToggleComments::default(), window, cx)
12537    });
12538    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12539    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12540    cx.assert_editor_state(
12541        &r#"
12542            <!-- ˇ<script> -->
12543                // ˇvar x = new Y();
12544            <!-- ˇ</script> -->
12545        "#
12546        .unindent(),
12547    );
12548}
12549
12550#[gpui::test]
12551fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12552    init_test(cx, |_| {});
12553
12554    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12555    let multibuffer = cx.new(|cx| {
12556        let mut multibuffer = MultiBuffer::new(ReadWrite);
12557        multibuffer.push_excerpts(
12558            buffer.clone(),
12559            [
12560                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12561                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12562            ],
12563            cx,
12564        );
12565        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12566        multibuffer
12567    });
12568
12569    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12570    editor.update_in(cx, |editor, window, cx| {
12571        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12572        editor.change_selections(None, window, cx, |s| {
12573            s.select_ranges([
12574                Point::new(0, 0)..Point::new(0, 0),
12575                Point::new(1, 0)..Point::new(1, 0),
12576            ])
12577        });
12578
12579        editor.handle_input("X", window, cx);
12580        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12581        assert_eq!(
12582            editor.selections.ranges(cx),
12583            [
12584                Point::new(0, 1)..Point::new(0, 1),
12585                Point::new(1, 1)..Point::new(1, 1),
12586            ]
12587        );
12588
12589        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12590        editor.change_selections(None, window, cx, |s| {
12591            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12592        });
12593        editor.backspace(&Default::default(), window, cx);
12594        assert_eq!(editor.text(cx), "Xa\nbbb");
12595        assert_eq!(
12596            editor.selections.ranges(cx),
12597            [Point::new(1, 0)..Point::new(1, 0)]
12598        );
12599
12600        editor.change_selections(None, window, cx, |s| {
12601            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12602        });
12603        editor.backspace(&Default::default(), window, cx);
12604        assert_eq!(editor.text(cx), "X\nbb");
12605        assert_eq!(
12606            editor.selections.ranges(cx),
12607            [Point::new(0, 1)..Point::new(0, 1)]
12608        );
12609    });
12610}
12611
12612#[gpui::test]
12613fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12614    init_test(cx, |_| {});
12615
12616    let markers = vec![('[', ']').into(), ('(', ')').into()];
12617    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12618        indoc! {"
12619            [aaaa
12620            (bbbb]
12621            cccc)",
12622        },
12623        markers.clone(),
12624    );
12625    let excerpt_ranges = markers.into_iter().map(|marker| {
12626        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12627        ExcerptRange::new(context.clone())
12628    });
12629    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12630    let multibuffer = cx.new(|cx| {
12631        let mut multibuffer = MultiBuffer::new(ReadWrite);
12632        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12633        multibuffer
12634    });
12635
12636    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12637    editor.update_in(cx, |editor, window, cx| {
12638        let (expected_text, selection_ranges) = marked_text_ranges(
12639            indoc! {"
12640                aaaa
12641                bˇbbb
12642                bˇbbˇb
12643                cccc"
12644            },
12645            true,
12646        );
12647        assert_eq!(editor.text(cx), expected_text);
12648        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12649
12650        editor.handle_input("X", window, cx);
12651
12652        let (expected_text, expected_selections) = marked_text_ranges(
12653            indoc! {"
12654                aaaa
12655                bXˇbbXb
12656                bXˇbbXˇb
12657                cccc"
12658            },
12659            false,
12660        );
12661        assert_eq!(editor.text(cx), expected_text);
12662        assert_eq!(editor.selections.ranges(cx), expected_selections);
12663
12664        editor.newline(&Newline, window, cx);
12665        let (expected_text, expected_selections) = marked_text_ranges(
12666            indoc! {"
12667                aaaa
12668                bX
12669                ˇbbX
12670                b
12671                bX
12672                ˇbbX
12673                ˇb
12674                cccc"
12675            },
12676            false,
12677        );
12678        assert_eq!(editor.text(cx), expected_text);
12679        assert_eq!(editor.selections.ranges(cx), expected_selections);
12680    });
12681}
12682
12683#[gpui::test]
12684fn test_refresh_selections(cx: &mut TestAppContext) {
12685    init_test(cx, |_| {});
12686
12687    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12688    let mut excerpt1_id = None;
12689    let multibuffer = cx.new(|cx| {
12690        let mut multibuffer = MultiBuffer::new(ReadWrite);
12691        excerpt1_id = multibuffer
12692            .push_excerpts(
12693                buffer.clone(),
12694                [
12695                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12696                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12697                ],
12698                cx,
12699            )
12700            .into_iter()
12701            .next();
12702        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12703        multibuffer
12704    });
12705
12706    let editor = cx.add_window(|window, cx| {
12707        let mut editor = build_editor(multibuffer.clone(), window, cx);
12708        let snapshot = editor.snapshot(window, cx);
12709        editor.change_selections(None, window, cx, |s| {
12710            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12711        });
12712        editor.begin_selection(
12713            Point::new(2, 1).to_display_point(&snapshot),
12714            true,
12715            1,
12716            window,
12717            cx,
12718        );
12719        assert_eq!(
12720            editor.selections.ranges(cx),
12721            [
12722                Point::new(1, 3)..Point::new(1, 3),
12723                Point::new(2, 1)..Point::new(2, 1),
12724            ]
12725        );
12726        editor
12727    });
12728
12729    // Refreshing selections is a no-op when excerpts haven't changed.
12730    _ = editor.update(cx, |editor, window, cx| {
12731        editor.change_selections(None, window, cx, |s| s.refresh());
12732        assert_eq!(
12733            editor.selections.ranges(cx),
12734            [
12735                Point::new(1, 3)..Point::new(1, 3),
12736                Point::new(2, 1)..Point::new(2, 1),
12737            ]
12738        );
12739    });
12740
12741    multibuffer.update(cx, |multibuffer, cx| {
12742        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12743    });
12744    _ = editor.update(cx, |editor, window, cx| {
12745        // Removing an excerpt causes the first selection to become degenerate.
12746        assert_eq!(
12747            editor.selections.ranges(cx),
12748            [
12749                Point::new(0, 0)..Point::new(0, 0),
12750                Point::new(0, 1)..Point::new(0, 1)
12751            ]
12752        );
12753
12754        // Refreshing selections will relocate the first selection to the original buffer
12755        // location.
12756        editor.change_selections(None, window, cx, |s| s.refresh());
12757        assert_eq!(
12758            editor.selections.ranges(cx),
12759            [
12760                Point::new(0, 1)..Point::new(0, 1),
12761                Point::new(0, 3)..Point::new(0, 3)
12762            ]
12763        );
12764        assert!(editor.selections.pending_anchor().is_some());
12765    });
12766}
12767
12768#[gpui::test]
12769fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12770    init_test(cx, |_| {});
12771
12772    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12773    let mut excerpt1_id = None;
12774    let multibuffer = cx.new(|cx| {
12775        let mut multibuffer = MultiBuffer::new(ReadWrite);
12776        excerpt1_id = multibuffer
12777            .push_excerpts(
12778                buffer.clone(),
12779                [
12780                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12781                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12782                ],
12783                cx,
12784            )
12785            .into_iter()
12786            .next();
12787        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12788        multibuffer
12789    });
12790
12791    let editor = cx.add_window(|window, cx| {
12792        let mut editor = build_editor(multibuffer.clone(), window, cx);
12793        let snapshot = editor.snapshot(window, cx);
12794        editor.begin_selection(
12795            Point::new(1, 3).to_display_point(&snapshot),
12796            false,
12797            1,
12798            window,
12799            cx,
12800        );
12801        assert_eq!(
12802            editor.selections.ranges(cx),
12803            [Point::new(1, 3)..Point::new(1, 3)]
12804        );
12805        editor
12806    });
12807
12808    multibuffer.update(cx, |multibuffer, cx| {
12809        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12810    });
12811    _ = editor.update(cx, |editor, window, cx| {
12812        assert_eq!(
12813            editor.selections.ranges(cx),
12814            [Point::new(0, 0)..Point::new(0, 0)]
12815        );
12816
12817        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12818        editor.change_selections(None, window, cx, |s| s.refresh());
12819        assert_eq!(
12820            editor.selections.ranges(cx),
12821            [Point::new(0, 3)..Point::new(0, 3)]
12822        );
12823        assert!(editor.selections.pending_anchor().is_some());
12824    });
12825}
12826
12827#[gpui::test]
12828async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12829    init_test(cx, |_| {});
12830
12831    let language = Arc::new(
12832        Language::new(
12833            LanguageConfig {
12834                brackets: BracketPairConfig {
12835                    pairs: vec![
12836                        BracketPair {
12837                            start: "{".to_string(),
12838                            end: "}".to_string(),
12839                            close: true,
12840                            surround: true,
12841                            newline: true,
12842                        },
12843                        BracketPair {
12844                            start: "/* ".to_string(),
12845                            end: " */".to_string(),
12846                            close: true,
12847                            surround: true,
12848                            newline: true,
12849                        },
12850                    ],
12851                    ..Default::default()
12852                },
12853                ..Default::default()
12854            },
12855            Some(tree_sitter_rust::LANGUAGE.into()),
12856        )
12857        .with_indents_query("")
12858        .unwrap(),
12859    );
12860
12861    let text = concat!(
12862        "{   }\n",     //
12863        "  x\n",       //
12864        "  /*   */\n", //
12865        "x\n",         //
12866        "{{} }\n",     //
12867    );
12868
12869    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12870    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12871    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12872    editor
12873        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12874        .await;
12875
12876    editor.update_in(cx, |editor, window, cx| {
12877        editor.change_selections(None, window, cx, |s| {
12878            s.select_display_ranges([
12879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12880                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12881                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12882            ])
12883        });
12884        editor.newline(&Newline, window, cx);
12885
12886        assert_eq!(
12887            editor.buffer().read(cx).read(cx).text(),
12888            concat!(
12889                "{ \n",    // Suppress rustfmt
12890                "\n",      //
12891                "}\n",     //
12892                "  x\n",   //
12893                "  /* \n", //
12894                "  \n",    //
12895                "  */\n",  //
12896                "x\n",     //
12897                "{{} \n",  //
12898                "}\n",     //
12899            )
12900        );
12901    });
12902}
12903
12904#[gpui::test]
12905fn test_highlighted_ranges(cx: &mut TestAppContext) {
12906    init_test(cx, |_| {});
12907
12908    let editor = cx.add_window(|window, cx| {
12909        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12910        build_editor(buffer.clone(), window, cx)
12911    });
12912
12913    _ = editor.update(cx, |editor, window, cx| {
12914        struct Type1;
12915        struct Type2;
12916
12917        let buffer = editor.buffer.read(cx).snapshot(cx);
12918
12919        let anchor_range =
12920            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12921
12922        editor.highlight_background::<Type1>(
12923            &[
12924                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12925                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12926                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12927                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12928            ],
12929            |_| Hsla::red(),
12930            cx,
12931        );
12932        editor.highlight_background::<Type2>(
12933            &[
12934                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12935                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12936                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12937                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12938            ],
12939            |_| Hsla::green(),
12940            cx,
12941        );
12942
12943        let snapshot = editor.snapshot(window, cx);
12944        let mut highlighted_ranges = editor.background_highlights_in_range(
12945            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12946            &snapshot,
12947            cx.theme().colors(),
12948        );
12949        // Enforce a consistent ordering based on color without relying on the ordering of the
12950        // highlight's `TypeId` which is non-executor.
12951        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12952        assert_eq!(
12953            highlighted_ranges,
12954            &[
12955                (
12956                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12957                    Hsla::red(),
12958                ),
12959                (
12960                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12961                    Hsla::red(),
12962                ),
12963                (
12964                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12965                    Hsla::green(),
12966                ),
12967                (
12968                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12969                    Hsla::green(),
12970                ),
12971            ]
12972        );
12973        assert_eq!(
12974            editor.background_highlights_in_range(
12975                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12976                &snapshot,
12977                cx.theme().colors(),
12978            ),
12979            &[(
12980                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12981                Hsla::red(),
12982            )]
12983        );
12984    });
12985}
12986
12987#[gpui::test]
12988async fn test_following(cx: &mut TestAppContext) {
12989    init_test(cx, |_| {});
12990
12991    let fs = FakeFs::new(cx.executor());
12992    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12993
12994    let buffer = project.update(cx, |project, cx| {
12995        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12996        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12997    });
12998    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
12999    let follower = cx.update(|cx| {
13000        cx.open_window(
13001            WindowOptions {
13002                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13003                    gpui::Point::new(px(0.), px(0.)),
13004                    gpui::Point::new(px(10.), px(80.)),
13005                ))),
13006                ..Default::default()
13007            },
13008            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13009        )
13010        .unwrap()
13011    });
13012
13013    let is_still_following = Rc::new(RefCell::new(true));
13014    let follower_edit_event_count = Rc::new(RefCell::new(0));
13015    let pending_update = Rc::new(RefCell::new(None));
13016    let leader_entity = leader.root(cx).unwrap();
13017    let follower_entity = follower.root(cx).unwrap();
13018    _ = follower.update(cx, {
13019        let update = pending_update.clone();
13020        let is_still_following = is_still_following.clone();
13021        let follower_edit_event_count = follower_edit_event_count.clone();
13022        |_, window, cx| {
13023            cx.subscribe_in(
13024                &leader_entity,
13025                window,
13026                move |_, leader, event, window, cx| {
13027                    leader.read(cx).add_event_to_update_proto(
13028                        event,
13029                        &mut update.borrow_mut(),
13030                        window,
13031                        cx,
13032                    );
13033                },
13034            )
13035            .detach();
13036
13037            cx.subscribe_in(
13038                &follower_entity,
13039                window,
13040                move |_, _, event: &EditorEvent, _window, _cx| {
13041                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13042                        *is_still_following.borrow_mut() = false;
13043                    }
13044
13045                    if let EditorEvent::BufferEdited = event {
13046                        *follower_edit_event_count.borrow_mut() += 1;
13047                    }
13048                },
13049            )
13050            .detach();
13051        }
13052    });
13053
13054    // Update the selections only
13055    _ = leader.update(cx, |leader, window, cx| {
13056        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13057    });
13058    follower
13059        .update(cx, |follower, window, cx| {
13060            follower.apply_update_proto(
13061                &project,
13062                pending_update.borrow_mut().take().unwrap(),
13063                window,
13064                cx,
13065            )
13066        })
13067        .unwrap()
13068        .await
13069        .unwrap();
13070    _ = follower.update(cx, |follower, _, cx| {
13071        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13072    });
13073    assert!(*is_still_following.borrow());
13074    assert_eq!(*follower_edit_event_count.borrow(), 0);
13075
13076    // Update the scroll position only
13077    _ = leader.update(cx, |leader, window, cx| {
13078        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13079    });
13080    follower
13081        .update(cx, |follower, window, cx| {
13082            follower.apply_update_proto(
13083                &project,
13084                pending_update.borrow_mut().take().unwrap(),
13085                window,
13086                cx,
13087            )
13088        })
13089        .unwrap()
13090        .await
13091        .unwrap();
13092    assert_eq!(
13093        follower
13094            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13095            .unwrap(),
13096        gpui::Point::new(1.5, 3.5)
13097    );
13098    assert!(*is_still_following.borrow());
13099    assert_eq!(*follower_edit_event_count.borrow(), 0);
13100
13101    // Update the selections and scroll position. The follower's scroll position is updated
13102    // via autoscroll, not via the leader's exact scroll position.
13103    _ = leader.update(cx, |leader, window, cx| {
13104        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13105        leader.request_autoscroll(Autoscroll::newest(), cx);
13106        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13107    });
13108    follower
13109        .update(cx, |follower, window, cx| {
13110            follower.apply_update_proto(
13111                &project,
13112                pending_update.borrow_mut().take().unwrap(),
13113                window,
13114                cx,
13115            )
13116        })
13117        .unwrap()
13118        .await
13119        .unwrap();
13120    _ = follower.update(cx, |follower, _, cx| {
13121        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13122        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13123    });
13124    assert!(*is_still_following.borrow());
13125
13126    // Creating a pending selection that precedes another selection
13127    _ = leader.update(cx, |leader, window, cx| {
13128        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13129        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13130    });
13131    follower
13132        .update(cx, |follower, window, cx| {
13133            follower.apply_update_proto(
13134                &project,
13135                pending_update.borrow_mut().take().unwrap(),
13136                window,
13137                cx,
13138            )
13139        })
13140        .unwrap()
13141        .await
13142        .unwrap();
13143    _ = follower.update(cx, |follower, _, cx| {
13144        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13145    });
13146    assert!(*is_still_following.borrow());
13147
13148    // Extend the pending selection so that it surrounds another selection
13149    _ = leader.update(cx, |leader, window, cx| {
13150        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13151    });
13152    follower
13153        .update(cx, |follower, window, cx| {
13154            follower.apply_update_proto(
13155                &project,
13156                pending_update.borrow_mut().take().unwrap(),
13157                window,
13158                cx,
13159            )
13160        })
13161        .unwrap()
13162        .await
13163        .unwrap();
13164    _ = follower.update(cx, |follower, _, cx| {
13165        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13166    });
13167
13168    // Scrolling locally breaks the follow
13169    _ = follower.update(cx, |follower, window, cx| {
13170        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13171        follower.set_scroll_anchor(
13172            ScrollAnchor {
13173                anchor: top_anchor,
13174                offset: gpui::Point::new(0.0, 0.5),
13175            },
13176            window,
13177            cx,
13178        );
13179    });
13180    assert!(!(*is_still_following.borrow()));
13181}
13182
13183#[gpui::test]
13184async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13185    init_test(cx, |_| {});
13186
13187    let fs = FakeFs::new(cx.executor());
13188    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13189    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13190    let pane = workspace
13191        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13192        .unwrap();
13193
13194    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13195
13196    let leader = pane.update_in(cx, |_, window, cx| {
13197        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13198        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13199    });
13200
13201    // Start following the editor when it has no excerpts.
13202    let mut state_message =
13203        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13204    let workspace_entity = workspace.root(cx).unwrap();
13205    let follower_1 = cx
13206        .update_window(*workspace.deref(), |_, window, cx| {
13207            Editor::from_state_proto(
13208                workspace_entity,
13209                ViewId {
13210                    creator: CollaboratorId::PeerId(PeerId::default()),
13211                    id: 0,
13212                },
13213                &mut state_message,
13214                window,
13215                cx,
13216            )
13217        })
13218        .unwrap()
13219        .unwrap()
13220        .await
13221        .unwrap();
13222
13223    let update_message = Rc::new(RefCell::new(None));
13224    follower_1.update_in(cx, {
13225        let update = update_message.clone();
13226        |_, window, cx| {
13227            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13228                leader.read(cx).add_event_to_update_proto(
13229                    event,
13230                    &mut update.borrow_mut(),
13231                    window,
13232                    cx,
13233                );
13234            })
13235            .detach();
13236        }
13237    });
13238
13239    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13240        (
13241            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13242            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13243        )
13244    });
13245
13246    // Insert some excerpts.
13247    leader.update(cx, |leader, cx| {
13248        leader.buffer.update(cx, |multibuffer, cx| {
13249            multibuffer.set_excerpts_for_path(
13250                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13251                buffer_1.clone(),
13252                vec![
13253                    Point::row_range(0..3),
13254                    Point::row_range(1..6),
13255                    Point::row_range(12..15),
13256                ],
13257                0,
13258                cx,
13259            );
13260            multibuffer.set_excerpts_for_path(
13261                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13262                buffer_2.clone(),
13263                vec![Point::row_range(0..6), Point::row_range(8..12)],
13264                0,
13265                cx,
13266            );
13267        });
13268    });
13269
13270    // Apply the update of adding the excerpts.
13271    follower_1
13272        .update_in(cx, |follower, window, cx| {
13273            follower.apply_update_proto(
13274                &project,
13275                update_message.borrow().clone().unwrap(),
13276                window,
13277                cx,
13278            )
13279        })
13280        .await
13281        .unwrap();
13282    assert_eq!(
13283        follower_1.update(cx, |editor, cx| editor.text(cx)),
13284        leader.update(cx, |editor, cx| editor.text(cx))
13285    );
13286    update_message.borrow_mut().take();
13287
13288    // Start following separately after it already has excerpts.
13289    let mut state_message =
13290        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13291    let workspace_entity = workspace.root(cx).unwrap();
13292    let follower_2 = cx
13293        .update_window(*workspace.deref(), |_, window, cx| {
13294            Editor::from_state_proto(
13295                workspace_entity,
13296                ViewId {
13297                    creator: CollaboratorId::PeerId(PeerId::default()),
13298                    id: 0,
13299                },
13300                &mut state_message,
13301                window,
13302                cx,
13303            )
13304        })
13305        .unwrap()
13306        .unwrap()
13307        .await
13308        .unwrap();
13309    assert_eq!(
13310        follower_2.update(cx, |editor, cx| editor.text(cx)),
13311        leader.update(cx, |editor, cx| editor.text(cx))
13312    );
13313
13314    // Remove some excerpts.
13315    leader.update(cx, |leader, cx| {
13316        leader.buffer.update(cx, |multibuffer, cx| {
13317            let excerpt_ids = multibuffer.excerpt_ids();
13318            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13319            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13320        });
13321    });
13322
13323    // Apply the update of removing the excerpts.
13324    follower_1
13325        .update_in(cx, |follower, window, cx| {
13326            follower.apply_update_proto(
13327                &project,
13328                update_message.borrow().clone().unwrap(),
13329                window,
13330                cx,
13331            )
13332        })
13333        .await
13334        .unwrap();
13335    follower_2
13336        .update_in(cx, |follower, window, cx| {
13337            follower.apply_update_proto(
13338                &project,
13339                update_message.borrow().clone().unwrap(),
13340                window,
13341                cx,
13342            )
13343        })
13344        .await
13345        .unwrap();
13346    update_message.borrow_mut().take();
13347    assert_eq!(
13348        follower_1.update(cx, |editor, cx| editor.text(cx)),
13349        leader.update(cx, |editor, cx| editor.text(cx))
13350    );
13351}
13352
13353#[gpui::test]
13354async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13355    init_test(cx, |_| {});
13356
13357    let mut cx = EditorTestContext::new(cx).await;
13358    let lsp_store =
13359        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13360
13361    cx.set_state(indoc! {"
13362        ˇfn func(abc def: i32) -> u32 {
13363        }
13364    "});
13365
13366    cx.update(|_, cx| {
13367        lsp_store.update(cx, |lsp_store, cx| {
13368            lsp_store
13369                .update_diagnostics(
13370                    LanguageServerId(0),
13371                    lsp::PublishDiagnosticsParams {
13372                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13373                        version: None,
13374                        diagnostics: vec![
13375                            lsp::Diagnostic {
13376                                range: lsp::Range::new(
13377                                    lsp::Position::new(0, 11),
13378                                    lsp::Position::new(0, 12),
13379                                ),
13380                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13381                                ..Default::default()
13382                            },
13383                            lsp::Diagnostic {
13384                                range: lsp::Range::new(
13385                                    lsp::Position::new(0, 12),
13386                                    lsp::Position::new(0, 15),
13387                                ),
13388                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13389                                ..Default::default()
13390                            },
13391                            lsp::Diagnostic {
13392                                range: lsp::Range::new(
13393                                    lsp::Position::new(0, 25),
13394                                    lsp::Position::new(0, 28),
13395                                ),
13396                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13397                                ..Default::default()
13398                            },
13399                        ],
13400                    },
13401                    &[],
13402                    cx,
13403                )
13404                .unwrap()
13405        });
13406    });
13407
13408    executor.run_until_parked();
13409
13410    cx.update_editor(|editor, window, cx| {
13411        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13412    });
13413
13414    cx.assert_editor_state(indoc! {"
13415        fn func(abc def: i32) -> ˇu32 {
13416        }
13417    "});
13418
13419    cx.update_editor(|editor, window, cx| {
13420        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13421    });
13422
13423    cx.assert_editor_state(indoc! {"
13424        fn func(abc ˇdef: i32) -> u32 {
13425        }
13426    "});
13427
13428    cx.update_editor(|editor, window, cx| {
13429        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13430    });
13431
13432    cx.assert_editor_state(indoc! {"
13433        fn func(abcˇ def: i32) -> u32 {
13434        }
13435    "});
13436
13437    cx.update_editor(|editor, window, cx| {
13438        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13439    });
13440
13441    cx.assert_editor_state(indoc! {"
13442        fn func(abc def: i32) -> ˇu32 {
13443        }
13444    "});
13445}
13446
13447#[gpui::test]
13448async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13449    init_test(cx, |_| {});
13450
13451    let mut cx = EditorTestContext::new(cx).await;
13452
13453    let diff_base = r#"
13454        use some::mod;
13455
13456        const A: u32 = 42;
13457
13458        fn main() {
13459            println!("hello");
13460
13461            println!("world");
13462        }
13463        "#
13464    .unindent();
13465
13466    // Edits are modified, removed, modified, added
13467    cx.set_state(
13468        &r#"
13469        use some::modified;
13470
13471        ˇ
13472        fn main() {
13473            println!("hello there");
13474
13475            println!("around the");
13476            println!("world");
13477        }
13478        "#
13479        .unindent(),
13480    );
13481
13482    cx.set_head_text(&diff_base);
13483    executor.run_until_parked();
13484
13485    cx.update_editor(|editor, window, cx| {
13486        //Wrap around the bottom of the buffer
13487        for _ in 0..3 {
13488            editor.go_to_next_hunk(&GoToHunk, window, cx);
13489        }
13490    });
13491
13492    cx.assert_editor_state(
13493        &r#"
13494        ˇuse some::modified;
13495
13496
13497        fn main() {
13498            println!("hello there");
13499
13500            println!("around the");
13501            println!("world");
13502        }
13503        "#
13504        .unindent(),
13505    );
13506
13507    cx.update_editor(|editor, window, cx| {
13508        //Wrap around the top of the buffer
13509        for _ in 0..2 {
13510            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13511        }
13512    });
13513
13514    cx.assert_editor_state(
13515        &r#"
13516        use some::modified;
13517
13518
13519        fn main() {
13520        ˇ    println!("hello there");
13521
13522            println!("around the");
13523            println!("world");
13524        }
13525        "#
13526        .unindent(),
13527    );
13528
13529    cx.update_editor(|editor, window, cx| {
13530        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13531    });
13532
13533    cx.assert_editor_state(
13534        &r#"
13535        use some::modified;
13536
13537        ˇ
13538        fn main() {
13539            println!("hello there");
13540
13541            println!("around the");
13542            println!("world");
13543        }
13544        "#
13545        .unindent(),
13546    );
13547
13548    cx.update_editor(|editor, window, cx| {
13549        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13550    });
13551
13552    cx.assert_editor_state(
13553        &r#"
13554        ˇuse some::modified;
13555
13556
13557        fn main() {
13558            println!("hello there");
13559
13560            println!("around the");
13561            println!("world");
13562        }
13563        "#
13564        .unindent(),
13565    );
13566
13567    cx.update_editor(|editor, window, cx| {
13568        for _ in 0..2 {
13569            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13570        }
13571    });
13572
13573    cx.assert_editor_state(
13574        &r#"
13575        use some::modified;
13576
13577
13578        fn main() {
13579        ˇ    println!("hello there");
13580
13581            println!("around the");
13582            println!("world");
13583        }
13584        "#
13585        .unindent(),
13586    );
13587
13588    cx.update_editor(|editor, window, cx| {
13589        editor.fold(&Fold, window, cx);
13590    });
13591
13592    cx.update_editor(|editor, window, cx| {
13593        editor.go_to_next_hunk(&GoToHunk, window, cx);
13594    });
13595
13596    cx.assert_editor_state(
13597        &r#"
13598        ˇuse some::modified;
13599
13600
13601        fn main() {
13602            println!("hello there");
13603
13604            println!("around the");
13605            println!("world");
13606        }
13607        "#
13608        .unindent(),
13609    );
13610}
13611
13612#[test]
13613fn test_split_words() {
13614    fn split(text: &str) -> Vec<&str> {
13615        split_words(text).collect()
13616    }
13617
13618    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13619    assert_eq!(split("hello_world"), &["hello_", "world"]);
13620    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13621    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13622    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13623    assert_eq!(split("helloworld"), &["helloworld"]);
13624
13625    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13626}
13627
13628#[gpui::test]
13629async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13630    init_test(cx, |_| {});
13631
13632    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13633    let mut assert = |before, after| {
13634        let _state_context = cx.set_state(before);
13635        cx.run_until_parked();
13636        cx.update_editor(|editor, window, cx| {
13637            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13638        });
13639        cx.run_until_parked();
13640        cx.assert_editor_state(after);
13641    };
13642
13643    // Outside bracket jumps to outside of matching bracket
13644    assert("console.logˇ(var);", "console.log(var)ˇ;");
13645    assert("console.log(var)ˇ;", "console.logˇ(var);");
13646
13647    // Inside bracket jumps to inside of matching bracket
13648    assert("console.log(ˇvar);", "console.log(varˇ);");
13649    assert("console.log(varˇ);", "console.log(ˇvar);");
13650
13651    // When outside a bracket and inside, favor jumping to the inside bracket
13652    assert(
13653        "console.log('foo', [1, 2, 3]ˇ);",
13654        "console.log(ˇ'foo', [1, 2, 3]);",
13655    );
13656    assert(
13657        "console.log(ˇ'foo', [1, 2, 3]);",
13658        "console.log('foo', [1, 2, 3]ˇ);",
13659    );
13660
13661    // Bias forward if two options are equally likely
13662    assert(
13663        "let result = curried_fun()ˇ();",
13664        "let result = curried_fun()()ˇ;",
13665    );
13666
13667    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13668    assert(
13669        indoc! {"
13670            function test() {
13671                console.log('test')ˇ
13672            }"},
13673        indoc! {"
13674            function test() {
13675                console.logˇ('test')
13676            }"},
13677    );
13678}
13679
13680#[gpui::test]
13681async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13682    init_test(cx, |_| {});
13683
13684    let fs = FakeFs::new(cx.executor());
13685    fs.insert_tree(
13686        path!("/a"),
13687        json!({
13688            "main.rs": "fn main() { let a = 5; }",
13689            "other.rs": "// Test file",
13690        }),
13691    )
13692    .await;
13693    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13694
13695    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13696    language_registry.add(Arc::new(Language::new(
13697        LanguageConfig {
13698            name: "Rust".into(),
13699            matcher: LanguageMatcher {
13700                path_suffixes: vec!["rs".to_string()],
13701                ..Default::default()
13702            },
13703            brackets: BracketPairConfig {
13704                pairs: vec![BracketPair {
13705                    start: "{".to_string(),
13706                    end: "}".to_string(),
13707                    close: true,
13708                    surround: true,
13709                    newline: true,
13710                }],
13711                disabled_scopes_by_bracket_ix: Vec::new(),
13712            },
13713            ..Default::default()
13714        },
13715        Some(tree_sitter_rust::LANGUAGE.into()),
13716    )));
13717    let mut fake_servers = language_registry.register_fake_lsp(
13718        "Rust",
13719        FakeLspAdapter {
13720            capabilities: lsp::ServerCapabilities {
13721                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13722                    first_trigger_character: "{".to_string(),
13723                    more_trigger_character: None,
13724                }),
13725                ..Default::default()
13726            },
13727            ..Default::default()
13728        },
13729    );
13730
13731    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13732
13733    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13734
13735    let worktree_id = workspace
13736        .update(cx, |workspace, _, cx| {
13737            workspace.project().update(cx, |project, cx| {
13738                project.worktrees(cx).next().unwrap().read(cx).id()
13739            })
13740        })
13741        .unwrap();
13742
13743    let buffer = project
13744        .update(cx, |project, cx| {
13745            project.open_local_buffer(path!("/a/main.rs"), cx)
13746        })
13747        .await
13748        .unwrap();
13749    let editor_handle = workspace
13750        .update(cx, |workspace, window, cx| {
13751            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13752        })
13753        .unwrap()
13754        .await
13755        .unwrap()
13756        .downcast::<Editor>()
13757        .unwrap();
13758
13759    cx.executor().start_waiting();
13760    let fake_server = fake_servers.next().await.unwrap();
13761
13762    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13763        |params, _| async move {
13764            assert_eq!(
13765                params.text_document_position.text_document.uri,
13766                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13767            );
13768            assert_eq!(
13769                params.text_document_position.position,
13770                lsp::Position::new(0, 21),
13771            );
13772
13773            Ok(Some(vec![lsp::TextEdit {
13774                new_text: "]".to_string(),
13775                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13776            }]))
13777        },
13778    );
13779
13780    editor_handle.update_in(cx, |editor, window, cx| {
13781        window.focus(&editor.focus_handle(cx));
13782        editor.change_selections(None, window, cx, |s| {
13783            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13784        });
13785        editor.handle_input("{", window, cx);
13786    });
13787
13788    cx.executor().run_until_parked();
13789
13790    buffer.update(cx, |buffer, _| {
13791        assert_eq!(
13792            buffer.text(),
13793            "fn main() { let a = {5}; }",
13794            "No extra braces from on type formatting should appear in the buffer"
13795        )
13796    });
13797}
13798
13799#[gpui::test]
13800async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13801    init_test(cx, |_| {});
13802
13803    let fs = FakeFs::new(cx.executor());
13804    fs.insert_tree(
13805        path!("/a"),
13806        json!({
13807            "main.rs": "fn main() { let a = 5; }",
13808            "other.rs": "// Test file",
13809        }),
13810    )
13811    .await;
13812
13813    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13814
13815    let server_restarts = Arc::new(AtomicUsize::new(0));
13816    let closure_restarts = Arc::clone(&server_restarts);
13817    let language_server_name = "test language server";
13818    let language_name: LanguageName = "Rust".into();
13819
13820    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13821    language_registry.add(Arc::new(Language::new(
13822        LanguageConfig {
13823            name: language_name.clone(),
13824            matcher: LanguageMatcher {
13825                path_suffixes: vec!["rs".to_string()],
13826                ..Default::default()
13827            },
13828            ..Default::default()
13829        },
13830        Some(tree_sitter_rust::LANGUAGE.into()),
13831    )));
13832    let mut fake_servers = language_registry.register_fake_lsp(
13833        "Rust",
13834        FakeLspAdapter {
13835            name: language_server_name,
13836            initialization_options: Some(json!({
13837                "testOptionValue": true
13838            })),
13839            initializer: Some(Box::new(move |fake_server| {
13840                let task_restarts = Arc::clone(&closure_restarts);
13841                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13842                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13843                    futures::future::ready(Ok(()))
13844                });
13845            })),
13846            ..Default::default()
13847        },
13848    );
13849
13850    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13851    let _buffer = project
13852        .update(cx, |project, cx| {
13853            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13854        })
13855        .await
13856        .unwrap();
13857    let _fake_server = fake_servers.next().await.unwrap();
13858    update_test_language_settings(cx, |language_settings| {
13859        language_settings.languages.insert(
13860            language_name.clone(),
13861            LanguageSettingsContent {
13862                tab_size: NonZeroU32::new(8),
13863                ..Default::default()
13864            },
13865        );
13866    });
13867    cx.executor().run_until_parked();
13868    assert_eq!(
13869        server_restarts.load(atomic::Ordering::Acquire),
13870        0,
13871        "Should not restart LSP server on an unrelated change"
13872    );
13873
13874    update_test_project_settings(cx, |project_settings| {
13875        project_settings.lsp.insert(
13876            "Some other server name".into(),
13877            LspSettings {
13878                binary: None,
13879                settings: None,
13880                initialization_options: Some(json!({
13881                    "some other init value": false
13882                })),
13883                enable_lsp_tasks: false,
13884            },
13885        );
13886    });
13887    cx.executor().run_until_parked();
13888    assert_eq!(
13889        server_restarts.load(atomic::Ordering::Acquire),
13890        0,
13891        "Should not restart LSP server on an unrelated LSP settings change"
13892    );
13893
13894    update_test_project_settings(cx, |project_settings| {
13895        project_settings.lsp.insert(
13896            language_server_name.into(),
13897            LspSettings {
13898                binary: None,
13899                settings: None,
13900                initialization_options: Some(json!({
13901                    "anotherInitValue": false
13902                })),
13903                enable_lsp_tasks: false,
13904            },
13905        );
13906    });
13907    cx.executor().run_until_parked();
13908    assert_eq!(
13909        server_restarts.load(atomic::Ordering::Acquire),
13910        1,
13911        "Should restart LSP server on a related LSP settings change"
13912    );
13913
13914    update_test_project_settings(cx, |project_settings| {
13915        project_settings.lsp.insert(
13916            language_server_name.into(),
13917            LspSettings {
13918                binary: None,
13919                settings: None,
13920                initialization_options: Some(json!({
13921                    "anotherInitValue": false
13922                })),
13923                enable_lsp_tasks: false,
13924            },
13925        );
13926    });
13927    cx.executor().run_until_parked();
13928    assert_eq!(
13929        server_restarts.load(atomic::Ordering::Acquire),
13930        1,
13931        "Should not restart LSP server on a related LSP settings change that is the same"
13932    );
13933
13934    update_test_project_settings(cx, |project_settings| {
13935        project_settings.lsp.insert(
13936            language_server_name.into(),
13937            LspSettings {
13938                binary: None,
13939                settings: None,
13940                initialization_options: None,
13941                enable_lsp_tasks: false,
13942            },
13943        );
13944    });
13945    cx.executor().run_until_parked();
13946    assert_eq!(
13947        server_restarts.load(atomic::Ordering::Acquire),
13948        2,
13949        "Should restart LSP server on another related LSP settings change"
13950    );
13951}
13952
13953#[gpui::test]
13954async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13955    init_test(cx, |_| {});
13956
13957    let mut cx = EditorLspTestContext::new_rust(
13958        lsp::ServerCapabilities {
13959            completion_provider: Some(lsp::CompletionOptions {
13960                trigger_characters: Some(vec![".".to_string()]),
13961                resolve_provider: Some(true),
13962                ..Default::default()
13963            }),
13964            ..Default::default()
13965        },
13966        cx,
13967    )
13968    .await;
13969
13970    cx.set_state("fn main() { let a = 2ˇ; }");
13971    cx.simulate_keystroke(".");
13972    let completion_item = lsp::CompletionItem {
13973        label: "some".into(),
13974        kind: Some(lsp::CompletionItemKind::SNIPPET),
13975        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13976        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13977            kind: lsp::MarkupKind::Markdown,
13978            value: "```rust\nSome(2)\n```".to_string(),
13979        })),
13980        deprecated: Some(false),
13981        sort_text: Some("fffffff2".to_string()),
13982        filter_text: Some("some".to_string()),
13983        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13984        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13985            range: lsp::Range {
13986                start: lsp::Position {
13987                    line: 0,
13988                    character: 22,
13989                },
13990                end: lsp::Position {
13991                    line: 0,
13992                    character: 22,
13993                },
13994            },
13995            new_text: "Some(2)".to_string(),
13996        })),
13997        additional_text_edits: Some(vec![lsp::TextEdit {
13998            range: lsp::Range {
13999                start: lsp::Position {
14000                    line: 0,
14001                    character: 20,
14002                },
14003                end: lsp::Position {
14004                    line: 0,
14005                    character: 22,
14006                },
14007            },
14008            new_text: "".to_string(),
14009        }]),
14010        ..Default::default()
14011    };
14012
14013    let closure_completion_item = completion_item.clone();
14014    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14015        let task_completion_item = closure_completion_item.clone();
14016        async move {
14017            Ok(Some(lsp::CompletionResponse::Array(vec![
14018                task_completion_item,
14019            ])))
14020        }
14021    });
14022
14023    request.next().await;
14024
14025    cx.condition(|editor, _| editor.context_menu_visible())
14026        .await;
14027    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14028        editor
14029            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14030            .unwrap()
14031    });
14032    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14033
14034    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14035        let task_completion_item = completion_item.clone();
14036        async move { Ok(task_completion_item) }
14037    })
14038    .next()
14039    .await
14040    .unwrap();
14041    apply_additional_edits.await.unwrap();
14042    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14043}
14044
14045#[gpui::test]
14046async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14047    init_test(cx, |_| {});
14048
14049    let mut cx = EditorLspTestContext::new_rust(
14050        lsp::ServerCapabilities {
14051            completion_provider: Some(lsp::CompletionOptions {
14052                trigger_characters: Some(vec![".".to_string()]),
14053                resolve_provider: Some(true),
14054                ..Default::default()
14055            }),
14056            ..Default::default()
14057        },
14058        cx,
14059    )
14060    .await;
14061
14062    cx.set_state("fn main() { let a = 2ˇ; }");
14063    cx.simulate_keystroke(".");
14064
14065    let item1 = lsp::CompletionItem {
14066        label: "method id()".to_string(),
14067        filter_text: Some("id".to_string()),
14068        detail: None,
14069        documentation: None,
14070        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14071            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14072            new_text: ".id".to_string(),
14073        })),
14074        ..lsp::CompletionItem::default()
14075    };
14076
14077    let item2 = lsp::CompletionItem {
14078        label: "other".to_string(),
14079        filter_text: Some("other".to_string()),
14080        detail: None,
14081        documentation: None,
14082        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14083            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14084            new_text: ".other".to_string(),
14085        })),
14086        ..lsp::CompletionItem::default()
14087    };
14088
14089    let item1 = item1.clone();
14090    cx.set_request_handler::<lsp::request::Completion, _, _>({
14091        let item1 = item1.clone();
14092        move |_, _, _| {
14093            let item1 = item1.clone();
14094            let item2 = item2.clone();
14095            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14096        }
14097    })
14098    .next()
14099    .await;
14100
14101    cx.condition(|editor, _| editor.context_menu_visible())
14102        .await;
14103    cx.update_editor(|editor, _, _| {
14104        let context_menu = editor.context_menu.borrow_mut();
14105        let context_menu = context_menu
14106            .as_ref()
14107            .expect("Should have the context menu deployed");
14108        match context_menu {
14109            CodeContextMenu::Completions(completions_menu) => {
14110                let completions = completions_menu.completions.borrow_mut();
14111                assert_eq!(
14112                    completions
14113                        .iter()
14114                        .map(|completion| &completion.label.text)
14115                        .collect::<Vec<_>>(),
14116                    vec!["method id()", "other"]
14117                )
14118            }
14119            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14120        }
14121    });
14122
14123    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14124        let item1 = item1.clone();
14125        move |_, item_to_resolve, _| {
14126            let item1 = item1.clone();
14127            async move {
14128                if item1 == item_to_resolve {
14129                    Ok(lsp::CompletionItem {
14130                        label: "method id()".to_string(),
14131                        filter_text: Some("id".to_string()),
14132                        detail: Some("Now resolved!".to_string()),
14133                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14134                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14135                            range: lsp::Range::new(
14136                                lsp::Position::new(0, 22),
14137                                lsp::Position::new(0, 22),
14138                            ),
14139                            new_text: ".id".to_string(),
14140                        })),
14141                        ..lsp::CompletionItem::default()
14142                    })
14143                } else {
14144                    Ok(item_to_resolve)
14145                }
14146            }
14147        }
14148    })
14149    .next()
14150    .await
14151    .unwrap();
14152    cx.run_until_parked();
14153
14154    cx.update_editor(|editor, window, cx| {
14155        editor.context_menu_next(&Default::default(), window, cx);
14156    });
14157
14158    cx.update_editor(|editor, _, _| {
14159        let context_menu = editor.context_menu.borrow_mut();
14160        let context_menu = context_menu
14161            .as_ref()
14162            .expect("Should have the context menu deployed");
14163        match context_menu {
14164            CodeContextMenu::Completions(completions_menu) => {
14165                let completions = completions_menu.completions.borrow_mut();
14166                assert_eq!(
14167                    completions
14168                        .iter()
14169                        .map(|completion| &completion.label.text)
14170                        .collect::<Vec<_>>(),
14171                    vec!["method id() Now resolved!", "other"],
14172                    "Should update first completion label, but not second as the filter text did not match."
14173                );
14174            }
14175            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14176        }
14177    });
14178}
14179
14180#[gpui::test]
14181async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14182    init_test(cx, |_| {});
14183    let mut cx = EditorLspTestContext::new_rust(
14184        lsp::ServerCapabilities {
14185            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14186            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14187            completion_provider: Some(lsp::CompletionOptions {
14188                resolve_provider: Some(true),
14189                ..Default::default()
14190            }),
14191            ..Default::default()
14192        },
14193        cx,
14194    )
14195    .await;
14196    cx.set_state(indoc! {"
14197        struct TestStruct {
14198            field: i32
14199        }
14200
14201        fn mainˇ() {
14202            let unused_var = 42;
14203            let test_struct = TestStruct { field: 42 };
14204        }
14205    "});
14206    let symbol_range = cx.lsp_range(indoc! {"
14207        struct TestStruct {
14208            field: i32
14209        }
14210
14211        «fn main»() {
14212            let unused_var = 42;
14213            let test_struct = TestStruct { field: 42 };
14214        }
14215    "});
14216    let mut hover_requests =
14217        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14218            Ok(Some(lsp::Hover {
14219                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14220                    kind: lsp::MarkupKind::Markdown,
14221                    value: "Function documentation".to_string(),
14222                }),
14223                range: Some(symbol_range),
14224            }))
14225        });
14226
14227    // Case 1: Test that code action menu hide hover popover
14228    cx.dispatch_action(Hover);
14229    hover_requests.next().await;
14230    cx.condition(|editor, _| editor.hover_state.visible()).await;
14231    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14232        move |_, _, _| async move {
14233            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14234                lsp::CodeAction {
14235                    title: "Remove unused variable".to_string(),
14236                    kind: Some(CodeActionKind::QUICKFIX),
14237                    edit: Some(lsp::WorkspaceEdit {
14238                        changes: Some(
14239                            [(
14240                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14241                                vec![lsp::TextEdit {
14242                                    range: lsp::Range::new(
14243                                        lsp::Position::new(5, 4),
14244                                        lsp::Position::new(5, 27),
14245                                    ),
14246                                    new_text: "".to_string(),
14247                                }],
14248                            )]
14249                            .into_iter()
14250                            .collect(),
14251                        ),
14252                        ..Default::default()
14253                    }),
14254                    ..Default::default()
14255                },
14256            )]))
14257        },
14258    );
14259    cx.update_editor(|editor, window, cx| {
14260        editor.toggle_code_actions(
14261            &ToggleCodeActions {
14262                deployed_from: None,
14263                quick_launch: false,
14264            },
14265            window,
14266            cx,
14267        );
14268    });
14269    code_action_requests.next().await;
14270    cx.run_until_parked();
14271    cx.condition(|editor, _| editor.context_menu_visible())
14272        .await;
14273    cx.update_editor(|editor, _, _| {
14274        assert!(
14275            !editor.hover_state.visible(),
14276            "Hover popover should be hidden when code action menu is shown"
14277        );
14278        // Hide code actions
14279        editor.context_menu.take();
14280    });
14281
14282    // Case 2: Test that code completions hide hover popover
14283    cx.dispatch_action(Hover);
14284    hover_requests.next().await;
14285    cx.condition(|editor, _| editor.hover_state.visible()).await;
14286    let counter = Arc::new(AtomicUsize::new(0));
14287    let mut completion_requests =
14288        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14289            let counter = counter.clone();
14290            async move {
14291                counter.fetch_add(1, atomic::Ordering::Release);
14292                Ok(Some(lsp::CompletionResponse::Array(vec![
14293                    lsp::CompletionItem {
14294                        label: "main".into(),
14295                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14296                        detail: Some("() -> ()".to_string()),
14297                        ..Default::default()
14298                    },
14299                    lsp::CompletionItem {
14300                        label: "TestStruct".into(),
14301                        kind: Some(lsp::CompletionItemKind::STRUCT),
14302                        detail: Some("struct TestStruct".to_string()),
14303                        ..Default::default()
14304                    },
14305                ])))
14306            }
14307        });
14308    cx.update_editor(|editor, window, cx| {
14309        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14310    });
14311    completion_requests.next().await;
14312    cx.condition(|editor, _| editor.context_menu_visible())
14313        .await;
14314    cx.update_editor(|editor, _, _| {
14315        assert!(
14316            !editor.hover_state.visible(),
14317            "Hover popover should be hidden when completion menu is shown"
14318        );
14319    });
14320}
14321
14322#[gpui::test]
14323async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14324    init_test(cx, |_| {});
14325
14326    let mut cx = EditorLspTestContext::new_rust(
14327        lsp::ServerCapabilities {
14328            completion_provider: Some(lsp::CompletionOptions {
14329                trigger_characters: Some(vec![".".to_string()]),
14330                resolve_provider: Some(true),
14331                ..Default::default()
14332            }),
14333            ..Default::default()
14334        },
14335        cx,
14336    )
14337    .await;
14338
14339    cx.set_state("fn main() { let a = 2ˇ; }");
14340    cx.simulate_keystroke(".");
14341
14342    let unresolved_item_1 = lsp::CompletionItem {
14343        label: "id".to_string(),
14344        filter_text: Some("id".to_string()),
14345        detail: None,
14346        documentation: None,
14347        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14348            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14349            new_text: ".id".to_string(),
14350        })),
14351        ..lsp::CompletionItem::default()
14352    };
14353    let resolved_item_1 = lsp::CompletionItem {
14354        additional_text_edits: Some(vec![lsp::TextEdit {
14355            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14356            new_text: "!!".to_string(),
14357        }]),
14358        ..unresolved_item_1.clone()
14359    };
14360    let unresolved_item_2 = lsp::CompletionItem {
14361        label: "other".to_string(),
14362        filter_text: Some("other".to_string()),
14363        detail: None,
14364        documentation: None,
14365        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14366            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14367            new_text: ".other".to_string(),
14368        })),
14369        ..lsp::CompletionItem::default()
14370    };
14371    let resolved_item_2 = lsp::CompletionItem {
14372        additional_text_edits: Some(vec![lsp::TextEdit {
14373            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14374            new_text: "??".to_string(),
14375        }]),
14376        ..unresolved_item_2.clone()
14377    };
14378
14379    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14380    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14381    cx.lsp
14382        .server
14383        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14384            let unresolved_item_1 = unresolved_item_1.clone();
14385            let resolved_item_1 = resolved_item_1.clone();
14386            let unresolved_item_2 = unresolved_item_2.clone();
14387            let resolved_item_2 = resolved_item_2.clone();
14388            let resolve_requests_1 = resolve_requests_1.clone();
14389            let resolve_requests_2 = resolve_requests_2.clone();
14390            move |unresolved_request, _| {
14391                let unresolved_item_1 = unresolved_item_1.clone();
14392                let resolved_item_1 = resolved_item_1.clone();
14393                let unresolved_item_2 = unresolved_item_2.clone();
14394                let resolved_item_2 = resolved_item_2.clone();
14395                let resolve_requests_1 = resolve_requests_1.clone();
14396                let resolve_requests_2 = resolve_requests_2.clone();
14397                async move {
14398                    if unresolved_request == unresolved_item_1 {
14399                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14400                        Ok(resolved_item_1.clone())
14401                    } else if unresolved_request == unresolved_item_2 {
14402                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14403                        Ok(resolved_item_2.clone())
14404                    } else {
14405                        panic!("Unexpected completion item {unresolved_request:?}")
14406                    }
14407                }
14408            }
14409        })
14410        .detach();
14411
14412    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14413        let unresolved_item_1 = unresolved_item_1.clone();
14414        let unresolved_item_2 = unresolved_item_2.clone();
14415        async move {
14416            Ok(Some(lsp::CompletionResponse::Array(vec![
14417                unresolved_item_1,
14418                unresolved_item_2,
14419            ])))
14420        }
14421    })
14422    .next()
14423    .await;
14424
14425    cx.condition(|editor, _| editor.context_menu_visible())
14426        .await;
14427    cx.update_editor(|editor, _, _| {
14428        let context_menu = editor.context_menu.borrow_mut();
14429        let context_menu = context_menu
14430            .as_ref()
14431            .expect("Should have the context menu deployed");
14432        match context_menu {
14433            CodeContextMenu::Completions(completions_menu) => {
14434                let completions = completions_menu.completions.borrow_mut();
14435                assert_eq!(
14436                    completions
14437                        .iter()
14438                        .map(|completion| &completion.label.text)
14439                        .collect::<Vec<_>>(),
14440                    vec!["id", "other"]
14441                )
14442            }
14443            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14444        }
14445    });
14446    cx.run_until_parked();
14447
14448    cx.update_editor(|editor, window, cx| {
14449        editor.context_menu_next(&ContextMenuNext, window, cx);
14450    });
14451    cx.run_until_parked();
14452    cx.update_editor(|editor, window, cx| {
14453        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14454    });
14455    cx.run_until_parked();
14456    cx.update_editor(|editor, window, cx| {
14457        editor.context_menu_next(&ContextMenuNext, window, cx);
14458    });
14459    cx.run_until_parked();
14460    cx.update_editor(|editor, window, cx| {
14461        editor
14462            .compose_completion(&ComposeCompletion::default(), window, cx)
14463            .expect("No task returned")
14464    })
14465    .await
14466    .expect("Completion failed");
14467    cx.run_until_parked();
14468
14469    cx.update_editor(|editor, _, cx| {
14470        assert_eq!(
14471            resolve_requests_1.load(atomic::Ordering::Acquire),
14472            1,
14473            "Should always resolve once despite multiple selections"
14474        );
14475        assert_eq!(
14476            resolve_requests_2.load(atomic::Ordering::Acquire),
14477            1,
14478            "Should always resolve once after multiple selections and applying the completion"
14479        );
14480        assert_eq!(
14481            editor.text(cx),
14482            "fn main() { let a = ??.other; }",
14483            "Should use resolved data when applying the completion"
14484        );
14485    });
14486}
14487
14488#[gpui::test]
14489async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14490    init_test(cx, |_| {});
14491
14492    let item_0 = lsp::CompletionItem {
14493        label: "abs".into(),
14494        insert_text: Some("abs".into()),
14495        data: Some(json!({ "very": "special"})),
14496        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14497        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14498            lsp::InsertReplaceEdit {
14499                new_text: "abs".to_string(),
14500                insert: lsp::Range::default(),
14501                replace: lsp::Range::default(),
14502            },
14503        )),
14504        ..lsp::CompletionItem::default()
14505    };
14506    let items = iter::once(item_0.clone())
14507        .chain((11..51).map(|i| lsp::CompletionItem {
14508            label: format!("item_{}", i),
14509            insert_text: Some(format!("item_{}", i)),
14510            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14511            ..lsp::CompletionItem::default()
14512        }))
14513        .collect::<Vec<_>>();
14514
14515    let default_commit_characters = vec!["?".to_string()];
14516    let default_data = json!({ "default": "data"});
14517    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14518    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14519    let default_edit_range = lsp::Range {
14520        start: lsp::Position {
14521            line: 0,
14522            character: 5,
14523        },
14524        end: lsp::Position {
14525            line: 0,
14526            character: 5,
14527        },
14528    };
14529
14530    let mut cx = EditorLspTestContext::new_rust(
14531        lsp::ServerCapabilities {
14532            completion_provider: Some(lsp::CompletionOptions {
14533                trigger_characters: Some(vec![".".to_string()]),
14534                resolve_provider: Some(true),
14535                ..Default::default()
14536            }),
14537            ..Default::default()
14538        },
14539        cx,
14540    )
14541    .await;
14542
14543    cx.set_state("fn main() { let a = 2ˇ; }");
14544    cx.simulate_keystroke(".");
14545
14546    let completion_data = default_data.clone();
14547    let completion_characters = default_commit_characters.clone();
14548    let completion_items = items.clone();
14549    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14550        let default_data = completion_data.clone();
14551        let default_commit_characters = completion_characters.clone();
14552        let items = completion_items.clone();
14553        async move {
14554            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14555                items,
14556                item_defaults: Some(lsp::CompletionListItemDefaults {
14557                    data: Some(default_data.clone()),
14558                    commit_characters: Some(default_commit_characters.clone()),
14559                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14560                        default_edit_range,
14561                    )),
14562                    insert_text_format: Some(default_insert_text_format),
14563                    insert_text_mode: Some(default_insert_text_mode),
14564                }),
14565                ..lsp::CompletionList::default()
14566            })))
14567        }
14568    })
14569    .next()
14570    .await;
14571
14572    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14573    cx.lsp
14574        .server
14575        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14576            let closure_resolved_items = resolved_items.clone();
14577            move |item_to_resolve, _| {
14578                let closure_resolved_items = closure_resolved_items.clone();
14579                async move {
14580                    closure_resolved_items.lock().push(item_to_resolve.clone());
14581                    Ok(item_to_resolve)
14582                }
14583            }
14584        })
14585        .detach();
14586
14587    cx.condition(|editor, _| editor.context_menu_visible())
14588        .await;
14589    cx.run_until_parked();
14590    cx.update_editor(|editor, _, _| {
14591        let menu = editor.context_menu.borrow_mut();
14592        match menu.as_ref().expect("should have the completions menu") {
14593            CodeContextMenu::Completions(completions_menu) => {
14594                assert_eq!(
14595                    completions_menu
14596                        .entries
14597                        .borrow()
14598                        .iter()
14599                        .map(|mat| mat.string.clone())
14600                        .collect::<Vec<String>>(),
14601                    items
14602                        .iter()
14603                        .map(|completion| completion.label.clone())
14604                        .collect::<Vec<String>>()
14605                );
14606            }
14607            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14608        }
14609    });
14610    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14611    // with 4 from the end.
14612    assert_eq!(
14613        *resolved_items.lock(),
14614        [&items[0..16], &items[items.len() - 4..items.len()]]
14615            .concat()
14616            .iter()
14617            .cloned()
14618            .map(|mut item| {
14619                if item.data.is_none() {
14620                    item.data = Some(default_data.clone());
14621                }
14622                item
14623            })
14624            .collect::<Vec<lsp::CompletionItem>>(),
14625        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14626    );
14627    resolved_items.lock().clear();
14628
14629    cx.update_editor(|editor, window, cx| {
14630        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14631    });
14632    cx.run_until_parked();
14633    // Completions that have already been resolved are skipped.
14634    assert_eq!(
14635        *resolved_items.lock(),
14636        items[items.len() - 16..items.len() - 4]
14637            .iter()
14638            .cloned()
14639            .map(|mut item| {
14640                if item.data.is_none() {
14641                    item.data = Some(default_data.clone());
14642                }
14643                item
14644            })
14645            .collect::<Vec<lsp::CompletionItem>>()
14646    );
14647    resolved_items.lock().clear();
14648}
14649
14650#[gpui::test]
14651async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14652    init_test(cx, |_| {});
14653
14654    let mut cx = EditorLspTestContext::new(
14655        Language::new(
14656            LanguageConfig {
14657                matcher: LanguageMatcher {
14658                    path_suffixes: vec!["jsx".into()],
14659                    ..Default::default()
14660                },
14661                overrides: [(
14662                    "element".into(),
14663                    LanguageConfigOverride {
14664                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14665                        ..Default::default()
14666                    },
14667                )]
14668                .into_iter()
14669                .collect(),
14670                ..Default::default()
14671            },
14672            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14673        )
14674        .with_override_query("(jsx_self_closing_element) @element")
14675        .unwrap(),
14676        lsp::ServerCapabilities {
14677            completion_provider: Some(lsp::CompletionOptions {
14678                trigger_characters: Some(vec![":".to_string()]),
14679                ..Default::default()
14680            }),
14681            ..Default::default()
14682        },
14683        cx,
14684    )
14685    .await;
14686
14687    cx.lsp
14688        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14689            Ok(Some(lsp::CompletionResponse::Array(vec![
14690                lsp::CompletionItem {
14691                    label: "bg-blue".into(),
14692                    ..Default::default()
14693                },
14694                lsp::CompletionItem {
14695                    label: "bg-red".into(),
14696                    ..Default::default()
14697                },
14698                lsp::CompletionItem {
14699                    label: "bg-yellow".into(),
14700                    ..Default::default()
14701                },
14702            ])))
14703        });
14704
14705    cx.set_state(r#"<p class="bgˇ" />"#);
14706
14707    // Trigger completion when typing a dash, because the dash is an extra
14708    // word character in the 'element' scope, which contains the cursor.
14709    cx.simulate_keystroke("-");
14710    cx.executor().run_until_parked();
14711    cx.update_editor(|editor, _, _| {
14712        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14713        {
14714            assert_eq!(
14715                completion_menu_entries(&menu),
14716                &["bg-red", "bg-blue", "bg-yellow"]
14717            );
14718        } else {
14719            panic!("expected completion menu to be open");
14720        }
14721    });
14722
14723    cx.simulate_keystroke("l");
14724    cx.executor().run_until_parked();
14725    cx.update_editor(|editor, _, _| {
14726        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14727        {
14728            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14729        } else {
14730            panic!("expected completion menu to be open");
14731        }
14732    });
14733
14734    // When filtering completions, consider the character after the '-' to
14735    // be the start of a subword.
14736    cx.set_state(r#"<p class="yelˇ" />"#);
14737    cx.simulate_keystroke("l");
14738    cx.executor().run_until_parked();
14739    cx.update_editor(|editor, _, _| {
14740        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14741        {
14742            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14743        } else {
14744            panic!("expected completion menu to be open");
14745        }
14746    });
14747}
14748
14749fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14750    let entries = menu.entries.borrow();
14751    entries.iter().map(|mat| mat.string.clone()).collect()
14752}
14753
14754#[gpui::test]
14755async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14756    init_test(cx, |settings| {
14757        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14758            FormatterList(vec![Formatter::Prettier].into()),
14759        ))
14760    });
14761
14762    let fs = FakeFs::new(cx.executor());
14763    fs.insert_file(path!("/file.ts"), Default::default()).await;
14764
14765    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14766    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14767
14768    language_registry.add(Arc::new(Language::new(
14769        LanguageConfig {
14770            name: "TypeScript".into(),
14771            matcher: LanguageMatcher {
14772                path_suffixes: vec!["ts".to_string()],
14773                ..Default::default()
14774            },
14775            ..Default::default()
14776        },
14777        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14778    )));
14779    update_test_language_settings(cx, |settings| {
14780        settings.defaults.prettier = Some(PrettierSettings {
14781            allowed: true,
14782            ..PrettierSettings::default()
14783        });
14784    });
14785
14786    let test_plugin = "test_plugin";
14787    let _ = language_registry.register_fake_lsp(
14788        "TypeScript",
14789        FakeLspAdapter {
14790            prettier_plugins: vec![test_plugin],
14791            ..Default::default()
14792        },
14793    );
14794
14795    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14796    let buffer = project
14797        .update(cx, |project, cx| {
14798            project.open_local_buffer(path!("/file.ts"), cx)
14799        })
14800        .await
14801        .unwrap();
14802
14803    let buffer_text = "one\ntwo\nthree\n";
14804    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14805    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14806    editor.update_in(cx, |editor, window, cx| {
14807        editor.set_text(buffer_text, window, cx)
14808    });
14809
14810    editor
14811        .update_in(cx, |editor, window, cx| {
14812            editor.perform_format(
14813                project.clone(),
14814                FormatTrigger::Manual,
14815                FormatTarget::Buffers,
14816                window,
14817                cx,
14818            )
14819        })
14820        .unwrap()
14821        .await;
14822    assert_eq!(
14823        editor.update(cx, |editor, cx| editor.text(cx)),
14824        buffer_text.to_string() + prettier_format_suffix,
14825        "Test prettier formatting was not applied to the original buffer text",
14826    );
14827
14828    update_test_language_settings(cx, |settings| {
14829        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14830    });
14831    let format = editor.update_in(cx, |editor, window, cx| {
14832        editor.perform_format(
14833            project.clone(),
14834            FormatTrigger::Manual,
14835            FormatTarget::Buffers,
14836            window,
14837            cx,
14838        )
14839    });
14840    format.await.unwrap();
14841    assert_eq!(
14842        editor.update(cx, |editor, cx| editor.text(cx)),
14843        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14844        "Autoformatting (via test prettier) was not applied to the original buffer text",
14845    );
14846}
14847
14848#[gpui::test]
14849async fn test_addition_reverts(cx: &mut TestAppContext) {
14850    init_test(cx, |_| {});
14851    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14852    let base_text = indoc! {r#"
14853        struct Row;
14854        struct Row1;
14855        struct Row2;
14856
14857        struct Row4;
14858        struct Row5;
14859        struct Row6;
14860
14861        struct Row8;
14862        struct Row9;
14863        struct Row10;"#};
14864
14865    // When addition hunks are not adjacent to carets, no hunk revert is performed
14866    assert_hunk_revert(
14867        indoc! {r#"struct Row;
14868                   struct Row1;
14869                   struct Row1.1;
14870                   struct Row1.2;
14871                   struct Row2;ˇ
14872
14873                   struct Row4;
14874                   struct Row5;
14875                   struct Row6;
14876
14877                   struct Row8;
14878                   ˇstruct Row9;
14879                   struct Row9.1;
14880                   struct Row9.2;
14881                   struct Row9.3;
14882                   struct Row10;"#},
14883        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14884        indoc! {r#"struct Row;
14885                   struct Row1;
14886                   struct Row1.1;
14887                   struct Row1.2;
14888                   struct Row2;ˇ
14889
14890                   struct Row4;
14891                   struct Row5;
14892                   struct Row6;
14893
14894                   struct Row8;
14895                   ˇstruct Row9;
14896                   struct Row9.1;
14897                   struct Row9.2;
14898                   struct Row9.3;
14899                   struct Row10;"#},
14900        base_text,
14901        &mut cx,
14902    );
14903    // Same for selections
14904    assert_hunk_revert(
14905        indoc! {r#"struct Row;
14906                   struct Row1;
14907                   struct Row2;
14908                   struct Row2.1;
14909                   struct Row2.2;
14910                   «ˇ
14911                   struct Row4;
14912                   struct» Row5;
14913                   «struct Row6;
14914                   ˇ»
14915                   struct Row9.1;
14916                   struct Row9.2;
14917                   struct Row9.3;
14918                   struct Row8;
14919                   struct Row9;
14920                   struct Row10;"#},
14921        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14922        indoc! {r#"struct Row;
14923                   struct Row1;
14924                   struct Row2;
14925                   struct Row2.1;
14926                   struct Row2.2;
14927                   «ˇ
14928                   struct Row4;
14929                   struct» Row5;
14930                   «struct Row6;
14931                   ˇ»
14932                   struct Row9.1;
14933                   struct Row9.2;
14934                   struct Row9.3;
14935                   struct Row8;
14936                   struct Row9;
14937                   struct Row10;"#},
14938        base_text,
14939        &mut cx,
14940    );
14941
14942    // When carets and selections intersect the addition hunks, those are reverted.
14943    // Adjacent carets got merged.
14944    assert_hunk_revert(
14945        indoc! {r#"struct Row;
14946                   ˇ// something on the top
14947                   struct Row1;
14948                   struct Row2;
14949                   struct Roˇw3.1;
14950                   struct Row2.2;
14951                   struct Row2.3;ˇ
14952
14953                   struct Row4;
14954                   struct ˇRow5.1;
14955                   struct Row5.2;
14956                   struct «Rowˇ»5.3;
14957                   struct Row5;
14958                   struct Row6;
14959                   ˇ
14960                   struct Row9.1;
14961                   struct «Rowˇ»9.2;
14962                   struct «ˇRow»9.3;
14963                   struct Row8;
14964                   struct Row9;
14965                   «ˇ// something on bottom»
14966                   struct Row10;"#},
14967        vec![
14968            DiffHunkStatusKind::Added,
14969            DiffHunkStatusKind::Added,
14970            DiffHunkStatusKind::Added,
14971            DiffHunkStatusKind::Added,
14972            DiffHunkStatusKind::Added,
14973        ],
14974        indoc! {r#"struct Row;
14975                   ˇstruct Row1;
14976                   struct Row2;
14977                   ˇ
14978                   struct Row4;
14979                   ˇstruct Row5;
14980                   struct Row6;
14981                   ˇ
14982                   ˇstruct Row8;
14983                   struct Row9;
14984                   ˇstruct Row10;"#},
14985        base_text,
14986        &mut cx,
14987    );
14988}
14989
14990#[gpui::test]
14991async fn test_modification_reverts(cx: &mut TestAppContext) {
14992    init_test(cx, |_| {});
14993    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14994    let base_text = indoc! {r#"
14995        struct Row;
14996        struct Row1;
14997        struct Row2;
14998
14999        struct Row4;
15000        struct Row5;
15001        struct Row6;
15002
15003        struct Row8;
15004        struct Row9;
15005        struct Row10;"#};
15006
15007    // Modification hunks behave the same as the addition ones.
15008    assert_hunk_revert(
15009        indoc! {r#"struct Row;
15010                   struct Row1;
15011                   struct Row33;
15012                   ˇ
15013                   struct Row4;
15014                   struct Row5;
15015                   struct Row6;
15016                   ˇ
15017                   struct Row99;
15018                   struct Row9;
15019                   struct Row10;"#},
15020        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15021        indoc! {r#"struct Row;
15022                   struct Row1;
15023                   struct Row33;
15024                   ˇ
15025                   struct Row4;
15026                   struct Row5;
15027                   struct Row6;
15028                   ˇ
15029                   struct Row99;
15030                   struct Row9;
15031                   struct Row10;"#},
15032        base_text,
15033        &mut cx,
15034    );
15035    assert_hunk_revert(
15036        indoc! {r#"struct Row;
15037                   struct Row1;
15038                   struct Row33;
15039                   «ˇ
15040                   struct Row4;
15041                   struct» Row5;
15042                   «struct Row6;
15043                   ˇ»
15044                   struct Row99;
15045                   struct Row9;
15046                   struct Row10;"#},
15047        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15048        indoc! {r#"struct Row;
15049                   struct Row1;
15050                   struct Row33;
15051                   «ˇ
15052                   struct Row4;
15053                   struct» Row5;
15054                   «struct Row6;
15055                   ˇ»
15056                   struct Row99;
15057                   struct Row9;
15058                   struct Row10;"#},
15059        base_text,
15060        &mut cx,
15061    );
15062
15063    assert_hunk_revert(
15064        indoc! {r#"ˇstruct Row1.1;
15065                   struct Row1;
15066                   «ˇstr»uct Row22;
15067
15068                   struct ˇRow44;
15069                   struct Row5;
15070                   struct «Rˇ»ow66;ˇ
15071
15072                   «struˇ»ct Row88;
15073                   struct Row9;
15074                   struct Row1011;ˇ"#},
15075        vec![
15076            DiffHunkStatusKind::Modified,
15077            DiffHunkStatusKind::Modified,
15078            DiffHunkStatusKind::Modified,
15079            DiffHunkStatusKind::Modified,
15080            DiffHunkStatusKind::Modified,
15081            DiffHunkStatusKind::Modified,
15082        ],
15083        indoc! {r#"struct Row;
15084                   ˇstruct Row1;
15085                   struct Row2;
15086                   ˇ
15087                   struct Row4;
15088                   ˇstruct Row5;
15089                   struct Row6;
15090                   ˇ
15091                   struct Row8;
15092                   ˇstruct Row9;
15093                   struct Row10;ˇ"#},
15094        base_text,
15095        &mut cx,
15096    );
15097}
15098
15099#[gpui::test]
15100async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15101    init_test(cx, |_| {});
15102    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15103    let base_text = indoc! {r#"
15104        one
15105
15106        two
15107        three
15108        "#};
15109
15110    cx.set_head_text(base_text);
15111    cx.set_state("\nˇ\n");
15112    cx.executor().run_until_parked();
15113    cx.update_editor(|editor, _window, cx| {
15114        editor.expand_selected_diff_hunks(cx);
15115    });
15116    cx.executor().run_until_parked();
15117    cx.update_editor(|editor, window, cx| {
15118        editor.backspace(&Default::default(), window, cx);
15119    });
15120    cx.run_until_parked();
15121    cx.assert_state_with_diff(
15122        indoc! {r#"
15123
15124        - two
15125        - threeˇ
15126        +
15127        "#}
15128        .to_string(),
15129    );
15130}
15131
15132#[gpui::test]
15133async fn test_deletion_reverts(cx: &mut TestAppContext) {
15134    init_test(cx, |_| {});
15135    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15136    let base_text = indoc! {r#"struct Row;
15137struct Row1;
15138struct Row2;
15139
15140struct Row4;
15141struct Row5;
15142struct Row6;
15143
15144struct Row8;
15145struct Row9;
15146struct Row10;"#};
15147
15148    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15149    assert_hunk_revert(
15150        indoc! {r#"struct Row;
15151                   struct Row2;
15152
15153                   ˇstruct Row4;
15154                   struct Row5;
15155                   struct Row6;
15156                   ˇ
15157                   struct Row8;
15158                   struct Row10;"#},
15159        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15160        indoc! {r#"struct Row;
15161                   struct Row2;
15162
15163                   ˇstruct Row4;
15164                   struct Row5;
15165                   struct Row6;
15166                   ˇ
15167                   struct Row8;
15168                   struct Row10;"#},
15169        base_text,
15170        &mut cx,
15171    );
15172    assert_hunk_revert(
15173        indoc! {r#"struct Row;
15174                   struct Row2;
15175
15176                   «ˇstruct Row4;
15177                   struct» Row5;
15178                   «struct Row6;
15179                   ˇ»
15180                   struct Row8;
15181                   struct Row10;"#},
15182        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15183        indoc! {r#"struct Row;
15184                   struct Row2;
15185
15186                   «ˇstruct Row4;
15187                   struct» Row5;
15188                   «struct Row6;
15189                   ˇ»
15190                   struct Row8;
15191                   struct Row10;"#},
15192        base_text,
15193        &mut cx,
15194    );
15195
15196    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15197    assert_hunk_revert(
15198        indoc! {r#"struct Row;
15199                   ˇstruct Row2;
15200
15201                   struct Row4;
15202                   struct Row5;
15203                   struct Row6;
15204
15205                   struct Row8;ˇ
15206                   struct Row10;"#},
15207        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15208        indoc! {r#"struct Row;
15209                   struct Row1;
15210                   ˇstruct Row2;
15211
15212                   struct Row4;
15213                   struct Row5;
15214                   struct Row6;
15215
15216                   struct Row8;ˇ
15217                   struct Row9;
15218                   struct Row10;"#},
15219        base_text,
15220        &mut cx,
15221    );
15222    assert_hunk_revert(
15223        indoc! {r#"struct Row;
15224                   struct Row2«ˇ;
15225                   struct Row4;
15226                   struct» Row5;
15227                   «struct Row6;
15228
15229                   struct Row8;ˇ»
15230                   struct Row10;"#},
15231        vec![
15232            DiffHunkStatusKind::Deleted,
15233            DiffHunkStatusKind::Deleted,
15234            DiffHunkStatusKind::Deleted,
15235        ],
15236        indoc! {r#"struct Row;
15237                   struct Row1;
15238                   struct Row2«ˇ;
15239
15240                   struct Row4;
15241                   struct» Row5;
15242                   «struct Row6;
15243
15244                   struct Row8;ˇ»
15245                   struct Row9;
15246                   struct Row10;"#},
15247        base_text,
15248        &mut cx,
15249    );
15250}
15251
15252#[gpui::test]
15253async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15254    init_test(cx, |_| {});
15255
15256    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15257    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15258    let base_text_3 =
15259        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15260
15261    let text_1 = edit_first_char_of_every_line(base_text_1);
15262    let text_2 = edit_first_char_of_every_line(base_text_2);
15263    let text_3 = edit_first_char_of_every_line(base_text_3);
15264
15265    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15266    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15267    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15268
15269    let multibuffer = cx.new(|cx| {
15270        let mut multibuffer = MultiBuffer::new(ReadWrite);
15271        multibuffer.push_excerpts(
15272            buffer_1.clone(),
15273            [
15274                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15275                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15276                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15277            ],
15278            cx,
15279        );
15280        multibuffer.push_excerpts(
15281            buffer_2.clone(),
15282            [
15283                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15284                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15285                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15286            ],
15287            cx,
15288        );
15289        multibuffer.push_excerpts(
15290            buffer_3.clone(),
15291            [
15292                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15293                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15294                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15295            ],
15296            cx,
15297        );
15298        multibuffer
15299    });
15300
15301    let fs = FakeFs::new(cx.executor());
15302    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15303    let (editor, cx) = cx
15304        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15305    editor.update_in(cx, |editor, _window, cx| {
15306        for (buffer, diff_base) in [
15307            (buffer_1.clone(), base_text_1),
15308            (buffer_2.clone(), base_text_2),
15309            (buffer_3.clone(), base_text_3),
15310        ] {
15311            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15312            editor
15313                .buffer
15314                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15315        }
15316    });
15317    cx.executor().run_until_parked();
15318
15319    editor.update_in(cx, |editor, window, cx| {
15320        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}");
15321        editor.select_all(&SelectAll, window, cx);
15322        editor.git_restore(&Default::default(), window, cx);
15323    });
15324    cx.executor().run_until_parked();
15325
15326    // When all ranges are selected, all buffer hunks are reverted.
15327    editor.update(cx, |editor, cx| {
15328        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");
15329    });
15330    buffer_1.update(cx, |buffer, _| {
15331        assert_eq!(buffer.text(), base_text_1);
15332    });
15333    buffer_2.update(cx, |buffer, _| {
15334        assert_eq!(buffer.text(), base_text_2);
15335    });
15336    buffer_3.update(cx, |buffer, _| {
15337        assert_eq!(buffer.text(), base_text_3);
15338    });
15339
15340    editor.update_in(cx, |editor, window, cx| {
15341        editor.undo(&Default::default(), window, cx);
15342    });
15343
15344    editor.update_in(cx, |editor, window, cx| {
15345        editor.change_selections(None, window, cx, |s| {
15346            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15347        });
15348        editor.git_restore(&Default::default(), window, cx);
15349    });
15350
15351    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15352    // but not affect buffer_2 and its related excerpts.
15353    editor.update(cx, |editor, cx| {
15354        assert_eq!(
15355            editor.text(cx),
15356            "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}"
15357        );
15358    });
15359    buffer_1.update(cx, |buffer, _| {
15360        assert_eq!(buffer.text(), base_text_1);
15361    });
15362    buffer_2.update(cx, |buffer, _| {
15363        assert_eq!(
15364            buffer.text(),
15365            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15366        );
15367    });
15368    buffer_3.update(cx, |buffer, _| {
15369        assert_eq!(
15370            buffer.text(),
15371            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15372        );
15373    });
15374
15375    fn edit_first_char_of_every_line(text: &str) -> String {
15376        text.split('\n')
15377            .map(|line| format!("X{}", &line[1..]))
15378            .collect::<Vec<_>>()
15379            .join("\n")
15380    }
15381}
15382
15383#[gpui::test]
15384async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15385    init_test(cx, |_| {});
15386
15387    let cols = 4;
15388    let rows = 10;
15389    let sample_text_1 = sample_text(rows, cols, 'a');
15390    assert_eq!(
15391        sample_text_1,
15392        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15393    );
15394    let sample_text_2 = sample_text(rows, cols, 'l');
15395    assert_eq!(
15396        sample_text_2,
15397        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15398    );
15399    let sample_text_3 = sample_text(rows, cols, 'v');
15400    assert_eq!(
15401        sample_text_3,
15402        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15403    );
15404
15405    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15406    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15407    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15408
15409    let multi_buffer = cx.new(|cx| {
15410        let mut multibuffer = MultiBuffer::new(ReadWrite);
15411        multibuffer.push_excerpts(
15412            buffer_1.clone(),
15413            [
15414                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15415                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15416                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15417            ],
15418            cx,
15419        );
15420        multibuffer.push_excerpts(
15421            buffer_2.clone(),
15422            [
15423                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15424                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15425                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15426            ],
15427            cx,
15428        );
15429        multibuffer.push_excerpts(
15430            buffer_3.clone(),
15431            [
15432                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15433                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15434                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15435            ],
15436            cx,
15437        );
15438        multibuffer
15439    });
15440
15441    let fs = FakeFs::new(cx.executor());
15442    fs.insert_tree(
15443        "/a",
15444        json!({
15445            "main.rs": sample_text_1,
15446            "other.rs": sample_text_2,
15447            "lib.rs": sample_text_3,
15448        }),
15449    )
15450    .await;
15451    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15453    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15454    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15455        Editor::new(
15456            EditorMode::full(),
15457            multi_buffer,
15458            Some(project.clone()),
15459            window,
15460            cx,
15461        )
15462    });
15463    let multibuffer_item_id = workspace
15464        .update(cx, |workspace, window, cx| {
15465            assert!(
15466                workspace.active_item(cx).is_none(),
15467                "active item should be None before the first item is added"
15468            );
15469            workspace.add_item_to_active_pane(
15470                Box::new(multi_buffer_editor.clone()),
15471                None,
15472                true,
15473                window,
15474                cx,
15475            );
15476            let active_item = workspace
15477                .active_item(cx)
15478                .expect("should have an active item after adding the multi buffer");
15479            assert!(
15480                !active_item.is_singleton(cx),
15481                "A multi buffer was expected to active after adding"
15482            );
15483            active_item.item_id()
15484        })
15485        .unwrap();
15486    cx.executor().run_until_parked();
15487
15488    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15489        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15490            s.select_ranges(Some(1..2))
15491        });
15492        editor.open_excerpts(&OpenExcerpts, window, cx);
15493    });
15494    cx.executor().run_until_parked();
15495    let first_item_id = workspace
15496        .update(cx, |workspace, window, cx| {
15497            let active_item = workspace
15498                .active_item(cx)
15499                .expect("should have an active item after navigating into the 1st buffer");
15500            let first_item_id = active_item.item_id();
15501            assert_ne!(
15502                first_item_id, multibuffer_item_id,
15503                "Should navigate into the 1st buffer and activate it"
15504            );
15505            assert!(
15506                active_item.is_singleton(cx),
15507                "New active item should be a singleton buffer"
15508            );
15509            assert_eq!(
15510                active_item
15511                    .act_as::<Editor>(cx)
15512                    .expect("should have navigated into an editor for the 1st buffer")
15513                    .read(cx)
15514                    .text(cx),
15515                sample_text_1
15516            );
15517
15518            workspace
15519                .go_back(workspace.active_pane().downgrade(), window, cx)
15520                .detach_and_log_err(cx);
15521
15522            first_item_id
15523        })
15524        .unwrap();
15525    cx.executor().run_until_parked();
15526    workspace
15527        .update(cx, |workspace, _, cx| {
15528            let active_item = workspace
15529                .active_item(cx)
15530                .expect("should have an active item after navigating back");
15531            assert_eq!(
15532                active_item.item_id(),
15533                multibuffer_item_id,
15534                "Should navigate back to the multi buffer"
15535            );
15536            assert!(!active_item.is_singleton(cx));
15537        })
15538        .unwrap();
15539
15540    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15541        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15542            s.select_ranges(Some(39..40))
15543        });
15544        editor.open_excerpts(&OpenExcerpts, window, cx);
15545    });
15546    cx.executor().run_until_parked();
15547    let second_item_id = workspace
15548        .update(cx, |workspace, window, cx| {
15549            let active_item = workspace
15550                .active_item(cx)
15551                .expect("should have an active item after navigating into the 2nd buffer");
15552            let second_item_id = active_item.item_id();
15553            assert_ne!(
15554                second_item_id, multibuffer_item_id,
15555                "Should navigate away from the multibuffer"
15556            );
15557            assert_ne!(
15558                second_item_id, first_item_id,
15559                "Should navigate into the 2nd buffer and activate it"
15560            );
15561            assert!(
15562                active_item.is_singleton(cx),
15563                "New active item should be a singleton buffer"
15564            );
15565            assert_eq!(
15566                active_item
15567                    .act_as::<Editor>(cx)
15568                    .expect("should have navigated into an editor")
15569                    .read(cx)
15570                    .text(cx),
15571                sample_text_2
15572            );
15573
15574            workspace
15575                .go_back(workspace.active_pane().downgrade(), window, cx)
15576                .detach_and_log_err(cx);
15577
15578            second_item_id
15579        })
15580        .unwrap();
15581    cx.executor().run_until_parked();
15582    workspace
15583        .update(cx, |workspace, _, cx| {
15584            let active_item = workspace
15585                .active_item(cx)
15586                .expect("should have an active item after navigating back from the 2nd buffer");
15587            assert_eq!(
15588                active_item.item_id(),
15589                multibuffer_item_id,
15590                "Should navigate back from the 2nd buffer to the multi buffer"
15591            );
15592            assert!(!active_item.is_singleton(cx));
15593        })
15594        .unwrap();
15595
15596    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15597        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15598            s.select_ranges(Some(70..70))
15599        });
15600        editor.open_excerpts(&OpenExcerpts, window, cx);
15601    });
15602    cx.executor().run_until_parked();
15603    workspace
15604        .update(cx, |workspace, window, cx| {
15605            let active_item = workspace
15606                .active_item(cx)
15607                .expect("should have an active item after navigating into the 3rd buffer");
15608            let third_item_id = active_item.item_id();
15609            assert_ne!(
15610                third_item_id, multibuffer_item_id,
15611                "Should navigate into the 3rd buffer and activate it"
15612            );
15613            assert_ne!(third_item_id, first_item_id);
15614            assert_ne!(third_item_id, second_item_id);
15615            assert!(
15616                active_item.is_singleton(cx),
15617                "New active item should be a singleton buffer"
15618            );
15619            assert_eq!(
15620                active_item
15621                    .act_as::<Editor>(cx)
15622                    .expect("should have navigated into an editor")
15623                    .read(cx)
15624                    .text(cx),
15625                sample_text_3
15626            );
15627
15628            workspace
15629                .go_back(workspace.active_pane().downgrade(), window, cx)
15630                .detach_and_log_err(cx);
15631        })
15632        .unwrap();
15633    cx.executor().run_until_parked();
15634    workspace
15635        .update(cx, |workspace, _, cx| {
15636            let active_item = workspace
15637                .active_item(cx)
15638                .expect("should have an active item after navigating back from the 3rd buffer");
15639            assert_eq!(
15640                active_item.item_id(),
15641                multibuffer_item_id,
15642                "Should navigate back from the 3rd buffer to the multi buffer"
15643            );
15644            assert!(!active_item.is_singleton(cx));
15645        })
15646        .unwrap();
15647}
15648
15649#[gpui::test]
15650async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15651    init_test(cx, |_| {});
15652
15653    let mut cx = EditorTestContext::new(cx).await;
15654
15655    let diff_base = r#"
15656        use some::mod;
15657
15658        const A: u32 = 42;
15659
15660        fn main() {
15661            println!("hello");
15662
15663            println!("world");
15664        }
15665        "#
15666    .unindent();
15667
15668    cx.set_state(
15669        &r#"
15670        use some::modified;
15671
15672        ˇ
15673        fn main() {
15674            println!("hello there");
15675
15676            println!("around the");
15677            println!("world");
15678        }
15679        "#
15680        .unindent(),
15681    );
15682
15683    cx.set_head_text(&diff_base);
15684    executor.run_until_parked();
15685
15686    cx.update_editor(|editor, window, cx| {
15687        editor.go_to_next_hunk(&GoToHunk, window, cx);
15688        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15689    });
15690    executor.run_until_parked();
15691    cx.assert_state_with_diff(
15692        r#"
15693          use some::modified;
15694
15695
15696          fn main() {
15697        -     println!("hello");
15698        + ˇ    println!("hello there");
15699
15700              println!("around the");
15701              println!("world");
15702          }
15703        "#
15704        .unindent(),
15705    );
15706
15707    cx.update_editor(|editor, window, cx| {
15708        for _ in 0..2 {
15709            editor.go_to_next_hunk(&GoToHunk, window, cx);
15710            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15711        }
15712    });
15713    executor.run_until_parked();
15714    cx.assert_state_with_diff(
15715        r#"
15716        - use some::mod;
15717        + ˇuse some::modified;
15718
15719
15720          fn main() {
15721        -     println!("hello");
15722        +     println!("hello there");
15723
15724        +     println!("around the");
15725              println!("world");
15726          }
15727        "#
15728        .unindent(),
15729    );
15730
15731    cx.update_editor(|editor, window, cx| {
15732        editor.go_to_next_hunk(&GoToHunk, window, cx);
15733        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15734    });
15735    executor.run_until_parked();
15736    cx.assert_state_with_diff(
15737        r#"
15738        - use some::mod;
15739        + use some::modified;
15740
15741        - const A: u32 = 42;
15742          ˇ
15743          fn main() {
15744        -     println!("hello");
15745        +     println!("hello there");
15746
15747        +     println!("around the");
15748              println!("world");
15749          }
15750        "#
15751        .unindent(),
15752    );
15753
15754    cx.update_editor(|editor, window, cx| {
15755        editor.cancel(&Cancel, window, cx);
15756    });
15757
15758    cx.assert_state_with_diff(
15759        r#"
15760          use some::modified;
15761
15762          ˇ
15763          fn main() {
15764              println!("hello there");
15765
15766              println!("around the");
15767              println!("world");
15768          }
15769        "#
15770        .unindent(),
15771    );
15772}
15773
15774#[gpui::test]
15775async fn test_diff_base_change_with_expanded_diff_hunks(
15776    executor: BackgroundExecutor,
15777    cx: &mut TestAppContext,
15778) {
15779    init_test(cx, |_| {});
15780
15781    let mut cx = EditorTestContext::new(cx).await;
15782
15783    let diff_base = r#"
15784        use some::mod1;
15785        use some::mod2;
15786
15787        const A: u32 = 42;
15788        const B: u32 = 42;
15789        const C: u32 = 42;
15790
15791        fn main() {
15792            println!("hello");
15793
15794            println!("world");
15795        }
15796        "#
15797    .unindent();
15798
15799    cx.set_state(
15800        &r#"
15801        use some::mod2;
15802
15803        const A: u32 = 42;
15804        const C: u32 = 42;
15805
15806        fn main(ˇ) {
15807            //println!("hello");
15808
15809            println!("world");
15810            //
15811            //
15812        }
15813        "#
15814        .unindent(),
15815    );
15816
15817    cx.set_head_text(&diff_base);
15818    executor.run_until_parked();
15819
15820    cx.update_editor(|editor, window, cx| {
15821        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15822    });
15823    executor.run_until_parked();
15824    cx.assert_state_with_diff(
15825        r#"
15826        - use some::mod1;
15827          use some::mod2;
15828
15829          const A: u32 = 42;
15830        - const B: u32 = 42;
15831          const C: u32 = 42;
15832
15833          fn main(ˇ) {
15834        -     println!("hello");
15835        +     //println!("hello");
15836
15837              println!("world");
15838        +     //
15839        +     //
15840          }
15841        "#
15842        .unindent(),
15843    );
15844
15845    cx.set_head_text("new diff base!");
15846    executor.run_until_parked();
15847    cx.assert_state_with_diff(
15848        r#"
15849        - new diff base!
15850        + use some::mod2;
15851        +
15852        + const A: u32 = 42;
15853        + const C: u32 = 42;
15854        +
15855        + fn main(ˇ) {
15856        +     //println!("hello");
15857        +
15858        +     println!("world");
15859        +     //
15860        +     //
15861        + }
15862        "#
15863        .unindent(),
15864    );
15865}
15866
15867#[gpui::test]
15868async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15869    init_test(cx, |_| {});
15870
15871    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15872    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15873    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15874    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15875    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15876    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15877
15878    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15879    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15880    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15881
15882    let multi_buffer = cx.new(|cx| {
15883        let mut multibuffer = MultiBuffer::new(ReadWrite);
15884        multibuffer.push_excerpts(
15885            buffer_1.clone(),
15886            [
15887                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15888                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15889                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15890            ],
15891            cx,
15892        );
15893        multibuffer.push_excerpts(
15894            buffer_2.clone(),
15895            [
15896                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15897                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15898                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15899            ],
15900            cx,
15901        );
15902        multibuffer.push_excerpts(
15903            buffer_3.clone(),
15904            [
15905                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15906                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15907                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15908            ],
15909            cx,
15910        );
15911        multibuffer
15912    });
15913
15914    let editor =
15915        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15916    editor
15917        .update(cx, |editor, _window, cx| {
15918            for (buffer, diff_base) in [
15919                (buffer_1.clone(), file_1_old),
15920                (buffer_2.clone(), file_2_old),
15921                (buffer_3.clone(), file_3_old),
15922            ] {
15923                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15924                editor
15925                    .buffer
15926                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15927            }
15928        })
15929        .unwrap();
15930
15931    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15932    cx.run_until_parked();
15933
15934    cx.assert_editor_state(
15935        &"
15936            ˇaaa
15937            ccc
15938            ddd
15939
15940            ggg
15941            hhh
15942
15943
15944            lll
15945            mmm
15946            NNN
15947
15948            qqq
15949            rrr
15950
15951            uuu
15952            111
15953            222
15954            333
15955
15956            666
15957            777
15958
15959            000
15960            !!!"
15961        .unindent(),
15962    );
15963
15964    cx.update_editor(|editor, window, cx| {
15965        editor.select_all(&SelectAll, window, cx);
15966        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15967    });
15968    cx.executor().run_until_parked();
15969
15970    cx.assert_state_with_diff(
15971        "
15972            «aaa
15973          - bbb
15974            ccc
15975            ddd
15976
15977            ggg
15978            hhh
15979
15980
15981            lll
15982            mmm
15983          - nnn
15984          + NNN
15985
15986            qqq
15987            rrr
15988
15989            uuu
15990            111
15991            222
15992            333
15993
15994          + 666
15995            777
15996
15997            000
15998            !!!ˇ»"
15999            .unindent(),
16000    );
16001}
16002
16003#[gpui::test]
16004async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16005    init_test(cx, |_| {});
16006
16007    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16008    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16009
16010    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16011    let multi_buffer = cx.new(|cx| {
16012        let mut multibuffer = MultiBuffer::new(ReadWrite);
16013        multibuffer.push_excerpts(
16014            buffer.clone(),
16015            [
16016                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16017                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16018                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16019            ],
16020            cx,
16021        );
16022        multibuffer
16023    });
16024
16025    let editor =
16026        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16027    editor
16028        .update(cx, |editor, _window, cx| {
16029            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16030            editor
16031                .buffer
16032                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16033        })
16034        .unwrap();
16035
16036    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16037    cx.run_until_parked();
16038
16039    cx.update_editor(|editor, window, cx| {
16040        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16041    });
16042    cx.executor().run_until_parked();
16043
16044    // When the start of a hunk coincides with the start of its excerpt,
16045    // the hunk is expanded. When the start of a a hunk is earlier than
16046    // the start of its excerpt, the hunk is not expanded.
16047    cx.assert_state_with_diff(
16048        "
16049            ˇaaa
16050          - bbb
16051          + BBB
16052
16053          - ddd
16054          - eee
16055          + DDD
16056          + EEE
16057            fff
16058
16059            iii
16060        "
16061        .unindent(),
16062    );
16063}
16064
16065#[gpui::test]
16066async fn test_edits_around_expanded_insertion_hunks(
16067    executor: BackgroundExecutor,
16068    cx: &mut TestAppContext,
16069) {
16070    init_test(cx, |_| {});
16071
16072    let mut cx = EditorTestContext::new(cx).await;
16073
16074    let diff_base = r#"
16075        use some::mod1;
16076        use some::mod2;
16077
16078        const A: u32 = 42;
16079
16080        fn main() {
16081            println!("hello");
16082
16083            println!("world");
16084        }
16085        "#
16086    .unindent();
16087    executor.run_until_parked();
16088    cx.set_state(
16089        &r#"
16090        use some::mod1;
16091        use some::mod2;
16092
16093        const A: u32 = 42;
16094        const B: u32 = 42;
16095        const C: u32 = 42;
16096        ˇ
16097
16098        fn main() {
16099            println!("hello");
16100
16101            println!("world");
16102        }
16103        "#
16104        .unindent(),
16105    );
16106
16107    cx.set_head_text(&diff_base);
16108    executor.run_until_parked();
16109
16110    cx.update_editor(|editor, window, cx| {
16111        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16112    });
16113    executor.run_until_parked();
16114
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
16125        fn main() {
16126            println!("hello");
16127
16128            println!("world");
16129        }
16130      "#
16131        .unindent(),
16132    );
16133
16134    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16135    executor.run_until_parked();
16136
16137    cx.assert_state_with_diff(
16138        r#"
16139        use some::mod1;
16140        use some::mod2;
16141
16142        const A: u32 = 42;
16143      + const B: u32 = 42;
16144      + const C: u32 = 42;
16145      + const D: u32 = 42;
16146      + ˇ
16147
16148        fn main() {
16149            println!("hello");
16150
16151            println!("world");
16152        }
16153      "#
16154        .unindent(),
16155    );
16156
16157    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16158    executor.run_until_parked();
16159
16160    cx.assert_state_with_diff(
16161        r#"
16162        use some::mod1;
16163        use some::mod2;
16164
16165        const A: u32 = 42;
16166      + const B: u32 = 42;
16167      + const C: u32 = 42;
16168      + const D: u32 = 42;
16169      + const E: u32 = 42;
16170      + ˇ
16171
16172        fn main() {
16173            println!("hello");
16174
16175            println!("world");
16176        }
16177      "#
16178        .unindent(),
16179    );
16180
16181    cx.update_editor(|editor, window, cx| {
16182        editor.delete_line(&DeleteLine, window, cx);
16183    });
16184    executor.run_until_parked();
16185
16186    cx.assert_state_with_diff(
16187        r#"
16188        use some::mod1;
16189        use some::mod2;
16190
16191        const A: u32 = 42;
16192      + const B: u32 = 42;
16193      + const C: u32 = 42;
16194      + const D: u32 = 42;
16195      + const E: u32 = 42;
16196        ˇ
16197        fn main() {
16198            println!("hello");
16199
16200            println!("world");
16201        }
16202      "#
16203        .unindent(),
16204    );
16205
16206    cx.update_editor(|editor, window, cx| {
16207        editor.move_up(&MoveUp, window, cx);
16208        editor.delete_line(&DeleteLine, window, cx);
16209        editor.move_up(&MoveUp, window, cx);
16210        editor.delete_line(&DeleteLine, window, cx);
16211        editor.move_up(&MoveUp, window, cx);
16212        editor.delete_line(&DeleteLine, window, cx);
16213    });
16214    executor.run_until_parked();
16215    cx.assert_state_with_diff(
16216        r#"
16217        use some::mod1;
16218        use some::mod2;
16219
16220        const A: u32 = 42;
16221      + const B: u32 = 42;
16222        ˇ
16223        fn main() {
16224            println!("hello");
16225
16226            println!("world");
16227        }
16228      "#
16229        .unindent(),
16230    );
16231
16232    cx.update_editor(|editor, window, cx| {
16233        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16234        editor.delete_line(&DeleteLine, window, cx);
16235    });
16236    executor.run_until_parked();
16237    cx.assert_state_with_diff(
16238        r#"
16239        ˇ
16240        fn main() {
16241            println!("hello");
16242
16243            println!("world");
16244        }
16245      "#
16246        .unindent(),
16247    );
16248}
16249
16250#[gpui::test]
16251async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16252    init_test(cx, |_| {});
16253
16254    let mut cx = EditorTestContext::new(cx).await;
16255    cx.set_head_text(indoc! { "
16256        one
16257        two
16258        three
16259        four
16260        five
16261        "
16262    });
16263    cx.set_state(indoc! { "
16264        one
16265        ˇthree
16266        five
16267    "});
16268    cx.run_until_parked();
16269    cx.update_editor(|editor, window, cx| {
16270        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16271    });
16272    cx.assert_state_with_diff(
16273        indoc! { "
16274        one
16275      - two
16276        ˇthree
16277      - four
16278        five
16279    "}
16280        .to_string(),
16281    );
16282    cx.update_editor(|editor, window, cx| {
16283        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16284    });
16285
16286    cx.assert_state_with_diff(
16287        indoc! { "
16288        one
16289        ˇthree
16290        five
16291    "}
16292        .to_string(),
16293    );
16294
16295    cx.set_state(indoc! { "
16296        one
16297        ˇTWO
16298        three
16299        four
16300        five
16301    "});
16302    cx.run_until_parked();
16303    cx.update_editor(|editor, window, cx| {
16304        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16305    });
16306
16307    cx.assert_state_with_diff(
16308        indoc! { "
16309            one
16310          - two
16311          + ˇTWO
16312            three
16313            four
16314            five
16315        "}
16316        .to_string(),
16317    );
16318    cx.update_editor(|editor, window, cx| {
16319        editor.move_up(&Default::default(), window, cx);
16320        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16321    });
16322    cx.assert_state_with_diff(
16323        indoc! { "
16324            one
16325            ˇTWO
16326            three
16327            four
16328            five
16329        "}
16330        .to_string(),
16331    );
16332}
16333
16334#[gpui::test]
16335async fn test_edits_around_expanded_deletion_hunks(
16336    executor: BackgroundExecutor,
16337    cx: &mut TestAppContext,
16338) {
16339    init_test(cx, |_| {});
16340
16341    let mut cx = EditorTestContext::new(cx).await;
16342
16343    let diff_base = r#"
16344        use some::mod1;
16345        use some::mod2;
16346
16347        const A: u32 = 42;
16348        const B: u32 = 42;
16349        const C: u32 = 42;
16350
16351
16352        fn main() {
16353            println!("hello");
16354
16355            println!("world");
16356        }
16357    "#
16358    .unindent();
16359    executor.run_until_parked();
16360    cx.set_state(
16361        &r#"
16362        use some::mod1;
16363        use some::mod2;
16364
16365        ˇconst B: u32 = 42;
16366        const C: u32 = 42;
16367
16368
16369        fn main() {
16370            println!("hello");
16371
16372            println!("world");
16373        }
16374        "#
16375        .unindent(),
16376    );
16377
16378    cx.set_head_text(&diff_base);
16379    executor.run_until_parked();
16380
16381    cx.update_editor(|editor, window, cx| {
16382        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16383    });
16384    executor.run_until_parked();
16385
16386    cx.assert_state_with_diff(
16387        r#"
16388        use some::mod1;
16389        use some::mod2;
16390
16391      - const A: u32 = 42;
16392        ˇconst B: u32 = 42;
16393        const C: u32 = 42;
16394
16395
16396        fn main() {
16397            println!("hello");
16398
16399            println!("world");
16400        }
16401      "#
16402        .unindent(),
16403    );
16404
16405    cx.update_editor(|editor, window, cx| {
16406        editor.delete_line(&DeleteLine, window, cx);
16407    });
16408    executor.run_until_parked();
16409    cx.assert_state_with_diff(
16410        r#"
16411        use some::mod1;
16412        use some::mod2;
16413
16414      - const A: u32 = 42;
16415      - const B: u32 = 42;
16416        ˇconst C: u32 = 42;
16417
16418
16419        fn main() {
16420            println!("hello");
16421
16422            println!("world");
16423        }
16424      "#
16425        .unindent(),
16426    );
16427
16428    cx.update_editor(|editor, window, cx| {
16429        editor.delete_line(&DeleteLine, window, cx);
16430    });
16431    executor.run_until_parked();
16432    cx.assert_state_with_diff(
16433        r#"
16434        use some::mod1;
16435        use some::mod2;
16436
16437      - const A: u32 = 42;
16438      - const B: u32 = 42;
16439      - const C: u32 = 42;
16440        ˇ
16441
16442        fn main() {
16443            println!("hello");
16444
16445            println!("world");
16446        }
16447      "#
16448        .unindent(),
16449    );
16450
16451    cx.update_editor(|editor, window, cx| {
16452        editor.handle_input("replacement", window, cx);
16453    });
16454    executor.run_until_parked();
16455    cx.assert_state_with_diff(
16456        r#"
16457        use some::mod1;
16458        use some::mod2;
16459
16460      - const A: u32 = 42;
16461      - const B: u32 = 42;
16462      - const C: u32 = 42;
16463      -
16464      + replacementˇ
16465
16466        fn main() {
16467            println!("hello");
16468
16469            println!("world");
16470        }
16471      "#
16472        .unindent(),
16473    );
16474}
16475
16476#[gpui::test]
16477async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16478    init_test(cx, |_| {});
16479
16480    let mut cx = EditorTestContext::new(cx).await;
16481
16482    let base_text = r#"
16483        one
16484        two
16485        three
16486        four
16487        five
16488    "#
16489    .unindent();
16490    executor.run_until_parked();
16491    cx.set_state(
16492        &r#"
16493        one
16494        two
16495        fˇour
16496        five
16497        "#
16498        .unindent(),
16499    );
16500
16501    cx.set_head_text(&base_text);
16502    executor.run_until_parked();
16503
16504    cx.update_editor(|editor, window, cx| {
16505        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16506    });
16507    executor.run_until_parked();
16508
16509    cx.assert_state_with_diff(
16510        r#"
16511          one
16512          two
16513        - three
16514          fˇour
16515          five
16516        "#
16517        .unindent(),
16518    );
16519
16520    cx.update_editor(|editor, window, cx| {
16521        editor.backspace(&Backspace, window, cx);
16522        editor.backspace(&Backspace, window, cx);
16523    });
16524    executor.run_until_parked();
16525    cx.assert_state_with_diff(
16526        r#"
16527          one
16528          two
16529        - threeˇ
16530        - four
16531        + our
16532          five
16533        "#
16534        .unindent(),
16535    );
16536}
16537
16538#[gpui::test]
16539async fn test_edit_after_expanded_modification_hunk(
16540    executor: BackgroundExecutor,
16541    cx: &mut TestAppContext,
16542) {
16543    init_test(cx, |_| {});
16544
16545    let mut cx = EditorTestContext::new(cx).await;
16546
16547    let diff_base = r#"
16548        use some::mod1;
16549        use some::mod2;
16550
16551        const A: u32 = 42;
16552        const B: u32 = 42;
16553        const C: u32 = 42;
16554        const D: u32 = 42;
16555
16556
16557        fn main() {
16558            println!("hello");
16559
16560            println!("world");
16561        }"#
16562    .unindent();
16563
16564    cx.set_state(
16565        &r#"
16566        use some::mod1;
16567        use some::mod2;
16568
16569        const A: u32 = 42;
16570        const B: u32 = 42;
16571        const C: u32 = 43ˇ
16572        const D: u32 = 42;
16573
16574
16575        fn main() {
16576            println!("hello");
16577
16578            println!("world");
16579        }"#
16580        .unindent(),
16581    );
16582
16583    cx.set_head_text(&diff_base);
16584    executor.run_until_parked();
16585    cx.update_editor(|editor, window, cx| {
16586        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16587    });
16588    executor.run_until_parked();
16589
16590    cx.assert_state_with_diff(
16591        r#"
16592        use some::mod1;
16593        use some::mod2;
16594
16595        const A: u32 = 42;
16596        const B: u32 = 42;
16597      - const C: u32 = 42;
16598      + const C: u32 = 43ˇ
16599        const D: u32 = 42;
16600
16601
16602        fn main() {
16603            println!("hello");
16604
16605            println!("world");
16606        }"#
16607        .unindent(),
16608    );
16609
16610    cx.update_editor(|editor, window, cx| {
16611        editor.handle_input("\nnew_line\n", window, cx);
16612    });
16613    executor.run_until_parked();
16614
16615    cx.assert_state_with_diff(
16616        r#"
16617        use some::mod1;
16618        use some::mod2;
16619
16620        const A: u32 = 42;
16621        const B: u32 = 42;
16622      - const C: u32 = 42;
16623      + const C: u32 = 43
16624      + new_line
16625      + ˇ
16626        const D: u32 = 42;
16627
16628
16629        fn main() {
16630            println!("hello");
16631
16632            println!("world");
16633        }"#
16634        .unindent(),
16635    );
16636}
16637
16638#[gpui::test]
16639async fn test_stage_and_unstage_added_file_hunk(
16640    executor: BackgroundExecutor,
16641    cx: &mut TestAppContext,
16642) {
16643    init_test(cx, |_| {});
16644
16645    let mut cx = EditorTestContext::new(cx).await;
16646    cx.update_editor(|editor, _, cx| {
16647        editor.set_expand_all_diff_hunks(cx);
16648    });
16649
16650    let working_copy = r#"
16651            ˇfn main() {
16652                println!("hello, world!");
16653            }
16654        "#
16655    .unindent();
16656
16657    cx.set_state(&working_copy);
16658    executor.run_until_parked();
16659
16660    cx.assert_state_with_diff(
16661        r#"
16662            + ˇfn main() {
16663            +     println!("hello, world!");
16664            + }
16665        "#
16666        .unindent(),
16667    );
16668    cx.assert_index_text(None);
16669
16670    cx.update_editor(|editor, window, cx| {
16671        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16672    });
16673    executor.run_until_parked();
16674    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16675    cx.assert_state_with_diff(
16676        r#"
16677            + ˇfn main() {
16678            +     println!("hello, world!");
16679            + }
16680        "#
16681        .unindent(),
16682    );
16683
16684    cx.update_editor(|editor, window, cx| {
16685        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16686    });
16687    executor.run_until_parked();
16688    cx.assert_index_text(None);
16689}
16690
16691async fn setup_indent_guides_editor(
16692    text: &str,
16693    cx: &mut TestAppContext,
16694) -> (BufferId, EditorTestContext) {
16695    init_test(cx, |_| {});
16696
16697    let mut cx = EditorTestContext::new(cx).await;
16698
16699    let buffer_id = cx.update_editor(|editor, window, cx| {
16700        editor.set_text(text, window, cx);
16701        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16702
16703        buffer_ids[0]
16704    });
16705
16706    (buffer_id, cx)
16707}
16708
16709fn assert_indent_guides(
16710    range: Range<u32>,
16711    expected: Vec<IndentGuide>,
16712    active_indices: Option<Vec<usize>>,
16713    cx: &mut EditorTestContext,
16714) {
16715    let indent_guides = cx.update_editor(|editor, window, cx| {
16716        let snapshot = editor.snapshot(window, cx).display_snapshot;
16717        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16718            editor,
16719            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16720            true,
16721            &snapshot,
16722            cx,
16723        );
16724
16725        indent_guides.sort_by(|a, b| {
16726            a.depth.cmp(&b.depth).then(
16727                a.start_row
16728                    .cmp(&b.start_row)
16729                    .then(a.end_row.cmp(&b.end_row)),
16730            )
16731        });
16732        indent_guides
16733    });
16734
16735    if let Some(expected) = active_indices {
16736        let active_indices = cx.update_editor(|editor, window, cx| {
16737            let snapshot = editor.snapshot(window, cx).display_snapshot;
16738            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16739        });
16740
16741        assert_eq!(
16742            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16743            expected,
16744            "Active indent guide indices do not match"
16745        );
16746    }
16747
16748    assert_eq!(indent_guides, expected, "Indent guides do not match");
16749}
16750
16751fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16752    IndentGuide {
16753        buffer_id,
16754        start_row: MultiBufferRow(start_row),
16755        end_row: MultiBufferRow(end_row),
16756        depth,
16757        tab_size: 4,
16758        settings: IndentGuideSettings {
16759            enabled: true,
16760            line_width: 1,
16761            active_line_width: 1,
16762            ..Default::default()
16763        },
16764    }
16765}
16766
16767#[gpui::test]
16768async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16769    let (buffer_id, mut cx) = setup_indent_guides_editor(
16770        &"
16771        fn main() {
16772            let a = 1;
16773        }"
16774        .unindent(),
16775        cx,
16776    )
16777    .await;
16778
16779    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16780}
16781
16782#[gpui::test]
16783async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16784    let (buffer_id, mut cx) = setup_indent_guides_editor(
16785        &"
16786        fn main() {
16787            let a = 1;
16788            let b = 2;
16789        }"
16790        .unindent(),
16791        cx,
16792    )
16793    .await;
16794
16795    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16796}
16797
16798#[gpui::test]
16799async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16800    let (buffer_id, mut cx) = setup_indent_guides_editor(
16801        &"
16802        fn main() {
16803            let a = 1;
16804            if a == 3 {
16805                let b = 2;
16806            } else {
16807                let c = 3;
16808            }
16809        }"
16810        .unindent(),
16811        cx,
16812    )
16813    .await;
16814
16815    assert_indent_guides(
16816        0..8,
16817        vec![
16818            indent_guide(buffer_id, 1, 6, 0),
16819            indent_guide(buffer_id, 3, 3, 1),
16820            indent_guide(buffer_id, 5, 5, 1),
16821        ],
16822        None,
16823        &mut cx,
16824    );
16825}
16826
16827#[gpui::test]
16828async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16829    let (buffer_id, mut cx) = setup_indent_guides_editor(
16830        &"
16831        fn main() {
16832            let a = 1;
16833                let b = 2;
16834            let c = 3;
16835        }"
16836        .unindent(),
16837        cx,
16838    )
16839    .await;
16840
16841    assert_indent_guides(
16842        0..5,
16843        vec![
16844            indent_guide(buffer_id, 1, 3, 0),
16845            indent_guide(buffer_id, 2, 2, 1),
16846        ],
16847        None,
16848        &mut cx,
16849    );
16850}
16851
16852#[gpui::test]
16853async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16854    let (buffer_id, mut cx) = setup_indent_guides_editor(
16855        &"
16856        fn main() {
16857            let a = 1;
16858
16859            let c = 3;
16860        }"
16861        .unindent(),
16862        cx,
16863    )
16864    .await;
16865
16866    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16867}
16868
16869#[gpui::test]
16870async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16871    let (buffer_id, mut cx) = setup_indent_guides_editor(
16872        &"
16873        fn main() {
16874            let a = 1;
16875
16876            let c = 3;
16877
16878            if a == 3 {
16879                let b = 2;
16880            } else {
16881                let c = 3;
16882            }
16883        }"
16884        .unindent(),
16885        cx,
16886    )
16887    .await;
16888
16889    assert_indent_guides(
16890        0..11,
16891        vec![
16892            indent_guide(buffer_id, 1, 9, 0),
16893            indent_guide(buffer_id, 6, 6, 1),
16894            indent_guide(buffer_id, 8, 8, 1),
16895        ],
16896        None,
16897        &mut cx,
16898    );
16899}
16900
16901#[gpui::test]
16902async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16903    let (buffer_id, mut cx) = setup_indent_guides_editor(
16904        &"
16905        fn main() {
16906            let a = 1;
16907
16908            let c = 3;
16909
16910            if a == 3 {
16911                let b = 2;
16912            } else {
16913                let c = 3;
16914            }
16915        }"
16916        .unindent(),
16917        cx,
16918    )
16919    .await;
16920
16921    assert_indent_guides(
16922        1..11,
16923        vec![
16924            indent_guide(buffer_id, 1, 9, 0),
16925            indent_guide(buffer_id, 6, 6, 1),
16926            indent_guide(buffer_id, 8, 8, 1),
16927        ],
16928        None,
16929        &mut cx,
16930    );
16931}
16932
16933#[gpui::test]
16934async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16935    let (buffer_id, mut cx) = setup_indent_guides_editor(
16936        &"
16937        fn main() {
16938            let a = 1;
16939
16940            let c = 3;
16941
16942            if a == 3 {
16943                let b = 2;
16944            } else {
16945                let c = 3;
16946            }
16947        }"
16948        .unindent(),
16949        cx,
16950    )
16951    .await;
16952
16953    assert_indent_guides(
16954        1..10,
16955        vec![
16956            indent_guide(buffer_id, 1, 9, 0),
16957            indent_guide(buffer_id, 6, 6, 1),
16958            indent_guide(buffer_id, 8, 8, 1),
16959        ],
16960        None,
16961        &mut cx,
16962    );
16963}
16964
16965#[gpui::test]
16966async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
16967    let (buffer_id, mut cx) = setup_indent_guides_editor(
16968        &"
16969        fn main() {
16970            if a {
16971                b(
16972                    c,
16973                    d,
16974                )
16975            } else {
16976                e(
16977                    f
16978                )
16979            }
16980        }"
16981        .unindent(),
16982        cx,
16983    )
16984    .await;
16985
16986    assert_indent_guides(
16987        0..11,
16988        vec![
16989            indent_guide(buffer_id, 1, 10, 0),
16990            indent_guide(buffer_id, 2, 5, 1),
16991            indent_guide(buffer_id, 7, 9, 1),
16992            indent_guide(buffer_id, 3, 4, 2),
16993            indent_guide(buffer_id, 8, 8, 2),
16994        ],
16995        None,
16996        &mut cx,
16997    );
16998
16999    cx.update_editor(|editor, window, cx| {
17000        editor.fold_at(MultiBufferRow(2), window, cx);
17001        assert_eq!(
17002            editor.display_text(cx),
17003            "
17004            fn main() {
17005                if a {
17006                    b(⋯
17007                    )
17008                } else {
17009                    e(
17010                        f
17011                    )
17012                }
17013            }"
17014            .unindent()
17015        );
17016    });
17017
17018    assert_indent_guides(
17019        0..11,
17020        vec![
17021            indent_guide(buffer_id, 1, 10, 0),
17022            indent_guide(buffer_id, 2, 5, 1),
17023            indent_guide(buffer_id, 7, 9, 1),
17024            indent_guide(buffer_id, 8, 8, 2),
17025        ],
17026        None,
17027        &mut cx,
17028    );
17029}
17030
17031#[gpui::test]
17032async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17033    let (buffer_id, mut cx) = setup_indent_guides_editor(
17034        &"
17035        block1
17036            block2
17037                block3
17038                    block4
17039            block2
17040        block1
17041        block1"
17042            .unindent(),
17043        cx,
17044    )
17045    .await;
17046
17047    assert_indent_guides(
17048        1..10,
17049        vec![
17050            indent_guide(buffer_id, 1, 4, 0),
17051            indent_guide(buffer_id, 2, 3, 1),
17052            indent_guide(buffer_id, 3, 3, 2),
17053        ],
17054        None,
17055        &mut cx,
17056    );
17057}
17058
17059#[gpui::test]
17060async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17061    let (buffer_id, mut cx) = setup_indent_guides_editor(
17062        &"
17063        block1
17064            block2
17065                block3
17066
17067        block1
17068        block1"
17069            .unindent(),
17070        cx,
17071    )
17072    .await;
17073
17074    assert_indent_guides(
17075        0..6,
17076        vec![
17077            indent_guide(buffer_id, 1, 2, 0),
17078            indent_guide(buffer_id, 2, 2, 1),
17079        ],
17080        None,
17081        &mut cx,
17082    );
17083}
17084
17085#[gpui::test]
17086async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17087    let (buffer_id, mut cx) = setup_indent_guides_editor(
17088        &"
17089        block1
17090
17091
17092
17093            block2
17094        "
17095        .unindent(),
17096        cx,
17097    )
17098    .await;
17099
17100    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17101}
17102
17103#[gpui::test]
17104async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17105    let (buffer_id, mut cx) = setup_indent_guides_editor(
17106        &"
17107        def a:
17108        \tb = 3
17109        \tif True:
17110        \t\tc = 4
17111        \t\td = 5
17112        \tprint(b)
17113        "
17114        .unindent(),
17115        cx,
17116    )
17117    .await;
17118
17119    assert_indent_guides(
17120        0..6,
17121        vec![
17122            indent_guide(buffer_id, 1, 5, 0),
17123            indent_guide(buffer_id, 3, 4, 1),
17124        ],
17125        None,
17126        &mut cx,
17127    );
17128}
17129
17130#[gpui::test]
17131async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17132    let (buffer_id, mut cx) = setup_indent_guides_editor(
17133        &"
17134    fn main() {
17135        let a = 1;
17136    }"
17137        .unindent(),
17138        cx,
17139    )
17140    .await;
17141
17142    cx.update_editor(|editor, window, cx| {
17143        editor.change_selections(None, window, cx, |s| {
17144            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17145        });
17146    });
17147
17148    assert_indent_guides(
17149        0..3,
17150        vec![indent_guide(buffer_id, 1, 1, 0)],
17151        Some(vec![0]),
17152        &mut cx,
17153    );
17154}
17155
17156#[gpui::test]
17157async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17158    let (buffer_id, mut cx) = setup_indent_guides_editor(
17159        &"
17160    fn main() {
17161        if 1 == 2 {
17162            let a = 1;
17163        }
17164    }"
17165        .unindent(),
17166        cx,
17167    )
17168    .await;
17169
17170    cx.update_editor(|editor, window, cx| {
17171        editor.change_selections(None, window, cx, |s| {
17172            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17173        });
17174    });
17175
17176    assert_indent_guides(
17177        0..4,
17178        vec![
17179            indent_guide(buffer_id, 1, 3, 0),
17180            indent_guide(buffer_id, 2, 2, 1),
17181        ],
17182        Some(vec![1]),
17183        &mut cx,
17184    );
17185
17186    cx.update_editor(|editor, window, cx| {
17187        editor.change_selections(None, window, cx, |s| {
17188            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17189        });
17190    });
17191
17192    assert_indent_guides(
17193        0..4,
17194        vec![
17195            indent_guide(buffer_id, 1, 3, 0),
17196            indent_guide(buffer_id, 2, 2, 1),
17197        ],
17198        Some(vec![1]),
17199        &mut cx,
17200    );
17201
17202    cx.update_editor(|editor, window, cx| {
17203        editor.change_selections(None, window, cx, |s| {
17204            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17205        });
17206    });
17207
17208    assert_indent_guides(
17209        0..4,
17210        vec![
17211            indent_guide(buffer_id, 1, 3, 0),
17212            indent_guide(buffer_id, 2, 2, 1),
17213        ],
17214        Some(vec![0]),
17215        &mut cx,
17216    );
17217}
17218
17219#[gpui::test]
17220async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17221    let (buffer_id, mut cx) = setup_indent_guides_editor(
17222        &"
17223    fn main() {
17224        let a = 1;
17225
17226        let b = 2;
17227    }"
17228        .unindent(),
17229        cx,
17230    )
17231    .await;
17232
17233    cx.update_editor(|editor, window, cx| {
17234        editor.change_selections(None, window, cx, |s| {
17235            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17236        });
17237    });
17238
17239    assert_indent_guides(
17240        0..5,
17241        vec![indent_guide(buffer_id, 1, 3, 0)],
17242        Some(vec![0]),
17243        &mut cx,
17244    );
17245}
17246
17247#[gpui::test]
17248async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17249    let (buffer_id, mut cx) = setup_indent_guides_editor(
17250        &"
17251    def m:
17252        a = 1
17253        pass"
17254            .unindent(),
17255        cx,
17256    )
17257    .await;
17258
17259    cx.update_editor(|editor, window, cx| {
17260        editor.change_selections(None, window, cx, |s| {
17261            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17262        });
17263    });
17264
17265    assert_indent_guides(
17266        0..3,
17267        vec![indent_guide(buffer_id, 1, 2, 0)],
17268        Some(vec![0]),
17269        &mut cx,
17270    );
17271}
17272
17273#[gpui::test]
17274async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17275    init_test(cx, |_| {});
17276    let mut cx = EditorTestContext::new(cx).await;
17277    let text = indoc! {
17278        "
17279        impl A {
17280            fn b() {
17281                0;
17282                3;
17283                5;
17284                6;
17285                7;
17286            }
17287        }
17288        "
17289    };
17290    let base_text = indoc! {
17291        "
17292        impl A {
17293            fn b() {
17294                0;
17295                1;
17296                2;
17297                3;
17298                4;
17299            }
17300            fn c() {
17301                5;
17302                6;
17303                7;
17304            }
17305        }
17306        "
17307    };
17308
17309    cx.update_editor(|editor, window, cx| {
17310        editor.set_text(text, window, cx);
17311
17312        editor.buffer().update(cx, |multibuffer, cx| {
17313            let buffer = multibuffer.as_singleton().unwrap();
17314            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17315
17316            multibuffer.set_all_diff_hunks_expanded(cx);
17317            multibuffer.add_diff(diff, cx);
17318
17319            buffer.read(cx).remote_id()
17320        })
17321    });
17322    cx.run_until_parked();
17323
17324    cx.assert_state_with_diff(
17325        indoc! { "
17326          impl A {
17327              fn b() {
17328                  0;
17329        -         1;
17330        -         2;
17331                  3;
17332        -         4;
17333        -     }
17334        -     fn c() {
17335                  5;
17336                  6;
17337                  7;
17338              }
17339          }
17340          ˇ"
17341        }
17342        .to_string(),
17343    );
17344
17345    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17346        editor
17347            .snapshot(window, cx)
17348            .buffer_snapshot
17349            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17350            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17351            .collect::<Vec<_>>()
17352    });
17353    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17354    assert_eq!(
17355        actual_guides,
17356        vec![
17357            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17358            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17359            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17360        ]
17361    );
17362}
17363
17364#[gpui::test]
17365async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17366    init_test(cx, |_| {});
17367    let mut cx = EditorTestContext::new(cx).await;
17368
17369    let diff_base = r#"
17370        a
17371        b
17372        c
17373        "#
17374    .unindent();
17375
17376    cx.set_state(
17377        &r#"
17378        ˇA
17379        b
17380        C
17381        "#
17382        .unindent(),
17383    );
17384    cx.set_head_text(&diff_base);
17385    cx.update_editor(|editor, window, cx| {
17386        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17387    });
17388    executor.run_until_parked();
17389
17390    let both_hunks_expanded = r#"
17391        - a
17392        + ˇA
17393          b
17394        - c
17395        + C
17396        "#
17397    .unindent();
17398
17399    cx.assert_state_with_diff(both_hunks_expanded.clone());
17400
17401    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17402        let snapshot = editor.snapshot(window, cx);
17403        let hunks = editor
17404            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17405            .collect::<Vec<_>>();
17406        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17407        let buffer_id = hunks[0].buffer_id;
17408        hunks
17409            .into_iter()
17410            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17411            .collect::<Vec<_>>()
17412    });
17413    assert_eq!(hunk_ranges.len(), 2);
17414
17415    cx.update_editor(|editor, _, cx| {
17416        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17417    });
17418    executor.run_until_parked();
17419
17420    let second_hunk_expanded = r#"
17421          ˇA
17422          b
17423        - c
17424        + C
17425        "#
17426    .unindent();
17427
17428    cx.assert_state_with_diff(second_hunk_expanded);
17429
17430    cx.update_editor(|editor, _, cx| {
17431        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17432    });
17433    executor.run_until_parked();
17434
17435    cx.assert_state_with_diff(both_hunks_expanded.clone());
17436
17437    cx.update_editor(|editor, _, cx| {
17438        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17439    });
17440    executor.run_until_parked();
17441
17442    let first_hunk_expanded = r#"
17443        - a
17444        + ˇA
17445          b
17446          C
17447        "#
17448    .unindent();
17449
17450    cx.assert_state_with_diff(first_hunk_expanded);
17451
17452    cx.update_editor(|editor, _, cx| {
17453        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17454    });
17455    executor.run_until_parked();
17456
17457    cx.assert_state_with_diff(both_hunks_expanded);
17458
17459    cx.set_state(
17460        &r#"
17461        ˇA
17462        b
17463        "#
17464        .unindent(),
17465    );
17466    cx.run_until_parked();
17467
17468    // TODO this cursor position seems bad
17469    cx.assert_state_with_diff(
17470        r#"
17471        - ˇa
17472        + A
17473          b
17474        "#
17475        .unindent(),
17476    );
17477
17478    cx.update_editor(|editor, window, cx| {
17479        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17480    });
17481
17482    cx.assert_state_with_diff(
17483        r#"
17484            - ˇa
17485            + A
17486              b
17487            - c
17488            "#
17489        .unindent(),
17490    );
17491
17492    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17493        let snapshot = editor.snapshot(window, cx);
17494        let hunks = editor
17495            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17496            .collect::<Vec<_>>();
17497        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17498        let buffer_id = hunks[0].buffer_id;
17499        hunks
17500            .into_iter()
17501            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17502            .collect::<Vec<_>>()
17503    });
17504    assert_eq!(hunk_ranges.len(), 2);
17505
17506    cx.update_editor(|editor, _, cx| {
17507        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17508    });
17509    executor.run_until_parked();
17510
17511    cx.assert_state_with_diff(
17512        r#"
17513        - ˇa
17514        + A
17515          b
17516        "#
17517        .unindent(),
17518    );
17519}
17520
17521#[gpui::test]
17522async fn test_toggle_deletion_hunk_at_start_of_file(
17523    executor: BackgroundExecutor,
17524    cx: &mut TestAppContext,
17525) {
17526    init_test(cx, |_| {});
17527    let mut cx = EditorTestContext::new(cx).await;
17528
17529    let diff_base = r#"
17530        a
17531        b
17532        c
17533        "#
17534    .unindent();
17535
17536    cx.set_state(
17537        &r#"
17538        ˇb
17539        c
17540        "#
17541        .unindent(),
17542    );
17543    cx.set_head_text(&diff_base);
17544    cx.update_editor(|editor, window, cx| {
17545        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17546    });
17547    executor.run_until_parked();
17548
17549    let hunk_expanded = r#"
17550        - a
17551          ˇb
17552          c
17553        "#
17554    .unindent();
17555
17556    cx.assert_state_with_diff(hunk_expanded.clone());
17557
17558    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17559        let snapshot = editor.snapshot(window, cx);
17560        let hunks = editor
17561            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17562            .collect::<Vec<_>>();
17563        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17564        let buffer_id = hunks[0].buffer_id;
17565        hunks
17566            .into_iter()
17567            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17568            .collect::<Vec<_>>()
17569    });
17570    assert_eq!(hunk_ranges.len(), 1);
17571
17572    cx.update_editor(|editor, _, cx| {
17573        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17574    });
17575    executor.run_until_parked();
17576
17577    let hunk_collapsed = r#"
17578          ˇb
17579          c
17580        "#
17581    .unindent();
17582
17583    cx.assert_state_with_diff(hunk_collapsed);
17584
17585    cx.update_editor(|editor, _, cx| {
17586        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17587    });
17588    executor.run_until_parked();
17589
17590    cx.assert_state_with_diff(hunk_expanded.clone());
17591}
17592
17593#[gpui::test]
17594async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17595    init_test(cx, |_| {});
17596
17597    let fs = FakeFs::new(cx.executor());
17598    fs.insert_tree(
17599        path!("/test"),
17600        json!({
17601            ".git": {},
17602            "file-1": "ONE\n",
17603            "file-2": "TWO\n",
17604            "file-3": "THREE\n",
17605        }),
17606    )
17607    .await;
17608
17609    fs.set_head_for_repo(
17610        path!("/test/.git").as_ref(),
17611        &[
17612            ("file-1".into(), "one\n".into()),
17613            ("file-2".into(), "two\n".into()),
17614            ("file-3".into(), "three\n".into()),
17615        ],
17616    );
17617
17618    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17619    let mut buffers = vec![];
17620    for i in 1..=3 {
17621        let buffer = project
17622            .update(cx, |project, cx| {
17623                let path = format!(path!("/test/file-{}"), i);
17624                project.open_local_buffer(path, cx)
17625            })
17626            .await
17627            .unwrap();
17628        buffers.push(buffer);
17629    }
17630
17631    let multibuffer = cx.new(|cx| {
17632        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17633        multibuffer.set_all_diff_hunks_expanded(cx);
17634        for buffer in &buffers {
17635            let snapshot = buffer.read(cx).snapshot();
17636            multibuffer.set_excerpts_for_path(
17637                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17638                buffer.clone(),
17639                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17640                DEFAULT_MULTIBUFFER_CONTEXT,
17641                cx,
17642            );
17643        }
17644        multibuffer
17645    });
17646
17647    let editor = cx.add_window(|window, cx| {
17648        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17649    });
17650    cx.run_until_parked();
17651
17652    let snapshot = editor
17653        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17654        .unwrap();
17655    let hunks = snapshot
17656        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17657        .map(|hunk| match hunk {
17658            DisplayDiffHunk::Unfolded {
17659                display_row_range, ..
17660            } => display_row_range,
17661            DisplayDiffHunk::Folded { .. } => unreachable!(),
17662        })
17663        .collect::<Vec<_>>();
17664    assert_eq!(
17665        hunks,
17666        [
17667            DisplayRow(2)..DisplayRow(4),
17668            DisplayRow(7)..DisplayRow(9),
17669            DisplayRow(12)..DisplayRow(14),
17670        ]
17671    );
17672}
17673
17674#[gpui::test]
17675async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17676    init_test(cx, |_| {});
17677
17678    let mut cx = EditorTestContext::new(cx).await;
17679    cx.set_head_text(indoc! { "
17680        one
17681        two
17682        three
17683        four
17684        five
17685        "
17686    });
17687    cx.set_index_text(indoc! { "
17688        one
17689        two
17690        three
17691        four
17692        five
17693        "
17694    });
17695    cx.set_state(indoc! {"
17696        one
17697        TWO
17698        ˇTHREE
17699        FOUR
17700        five
17701    "});
17702    cx.run_until_parked();
17703    cx.update_editor(|editor, window, cx| {
17704        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17705    });
17706    cx.run_until_parked();
17707    cx.assert_index_text(Some(indoc! {"
17708        one
17709        TWO
17710        THREE
17711        FOUR
17712        five
17713    "}));
17714    cx.set_state(indoc! { "
17715        one
17716        TWO
17717        ˇTHREE-HUNDRED
17718        FOUR
17719        five
17720    "});
17721    cx.run_until_parked();
17722    cx.update_editor(|editor, window, cx| {
17723        let snapshot = editor.snapshot(window, cx);
17724        let hunks = editor
17725            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17726            .collect::<Vec<_>>();
17727        assert_eq!(hunks.len(), 1);
17728        assert_eq!(
17729            hunks[0].status(),
17730            DiffHunkStatus {
17731                kind: DiffHunkStatusKind::Modified,
17732                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17733            }
17734        );
17735
17736        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17737    });
17738    cx.run_until_parked();
17739    cx.assert_index_text(Some(indoc! {"
17740        one
17741        TWO
17742        THREE-HUNDRED
17743        FOUR
17744        five
17745    "}));
17746}
17747
17748#[gpui::test]
17749fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17750    init_test(cx, |_| {});
17751
17752    let editor = cx.add_window(|window, cx| {
17753        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17754        build_editor(buffer, window, cx)
17755    });
17756
17757    let render_args = Arc::new(Mutex::new(None));
17758    let snapshot = editor
17759        .update(cx, |editor, window, cx| {
17760            let snapshot = editor.buffer().read(cx).snapshot(cx);
17761            let range =
17762                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17763
17764            struct RenderArgs {
17765                row: MultiBufferRow,
17766                folded: bool,
17767                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17768            }
17769
17770            let crease = Crease::inline(
17771                range,
17772                FoldPlaceholder::test(),
17773                {
17774                    let toggle_callback = render_args.clone();
17775                    move |row, folded, callback, _window, _cx| {
17776                        *toggle_callback.lock() = Some(RenderArgs {
17777                            row,
17778                            folded,
17779                            callback,
17780                        });
17781                        div()
17782                    }
17783                },
17784                |_row, _folded, _window, _cx| div(),
17785            );
17786
17787            editor.insert_creases(Some(crease), cx);
17788            let snapshot = editor.snapshot(window, cx);
17789            let _div = snapshot.render_crease_toggle(
17790                MultiBufferRow(1),
17791                false,
17792                cx.entity().clone(),
17793                window,
17794                cx,
17795            );
17796            snapshot
17797        })
17798        .unwrap();
17799
17800    let render_args = render_args.lock().take().unwrap();
17801    assert_eq!(render_args.row, MultiBufferRow(1));
17802    assert!(!render_args.folded);
17803    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17804
17805    cx.update_window(*editor, |_, window, cx| {
17806        (render_args.callback)(true, window, cx)
17807    })
17808    .unwrap();
17809    let snapshot = editor
17810        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17811        .unwrap();
17812    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17813
17814    cx.update_window(*editor, |_, window, cx| {
17815        (render_args.callback)(false, window, cx)
17816    })
17817    .unwrap();
17818    let snapshot = editor
17819        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17820        .unwrap();
17821    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17822}
17823
17824#[gpui::test]
17825async fn test_input_text(cx: &mut TestAppContext) {
17826    init_test(cx, |_| {});
17827    let mut cx = EditorTestContext::new(cx).await;
17828
17829    cx.set_state(
17830        &r#"ˇone
17831        two
17832
17833        three
17834        fourˇ
17835        five
17836
17837        siˇx"#
17838            .unindent(),
17839    );
17840
17841    cx.dispatch_action(HandleInput(String::new()));
17842    cx.assert_editor_state(
17843        &r#"ˇone
17844        two
17845
17846        three
17847        fourˇ
17848        five
17849
17850        siˇx"#
17851            .unindent(),
17852    );
17853
17854    cx.dispatch_action(HandleInput("AAAA".to_string()));
17855    cx.assert_editor_state(
17856        &r#"AAAAˇone
17857        two
17858
17859        three
17860        fourAAAAˇ
17861        five
17862
17863        siAAAAˇx"#
17864            .unindent(),
17865    );
17866}
17867
17868#[gpui::test]
17869async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17870    init_test(cx, |_| {});
17871
17872    let mut cx = EditorTestContext::new(cx).await;
17873    cx.set_state(
17874        r#"let foo = 1;
17875let foo = 2;
17876let foo = 3;
17877let fooˇ = 4;
17878let foo = 5;
17879let foo = 6;
17880let foo = 7;
17881let foo = 8;
17882let foo = 9;
17883let foo = 10;
17884let foo = 11;
17885let foo = 12;
17886let foo = 13;
17887let foo = 14;
17888let foo = 15;"#,
17889    );
17890
17891    cx.update_editor(|e, window, cx| {
17892        assert_eq!(
17893            e.next_scroll_position,
17894            NextScrollCursorCenterTopBottom::Center,
17895            "Default next scroll direction is center",
17896        );
17897
17898        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17899        assert_eq!(
17900            e.next_scroll_position,
17901            NextScrollCursorCenterTopBottom::Top,
17902            "After center, next scroll direction should be top",
17903        );
17904
17905        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17906        assert_eq!(
17907            e.next_scroll_position,
17908            NextScrollCursorCenterTopBottom::Bottom,
17909            "After top, next scroll direction should be bottom",
17910        );
17911
17912        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17913        assert_eq!(
17914            e.next_scroll_position,
17915            NextScrollCursorCenterTopBottom::Center,
17916            "After bottom, scrolling should start over",
17917        );
17918
17919        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17920        assert_eq!(
17921            e.next_scroll_position,
17922            NextScrollCursorCenterTopBottom::Top,
17923            "Scrolling continues if retriggered fast enough"
17924        );
17925    });
17926
17927    cx.executor()
17928        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17929    cx.executor().run_until_parked();
17930    cx.update_editor(|e, _, _| {
17931        assert_eq!(
17932            e.next_scroll_position,
17933            NextScrollCursorCenterTopBottom::Center,
17934            "If scrolling is not triggered fast enough, it should reset"
17935        );
17936    });
17937}
17938
17939#[gpui::test]
17940async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17941    init_test(cx, |_| {});
17942    let mut cx = EditorLspTestContext::new_rust(
17943        lsp::ServerCapabilities {
17944            definition_provider: Some(lsp::OneOf::Left(true)),
17945            references_provider: Some(lsp::OneOf::Left(true)),
17946            ..lsp::ServerCapabilities::default()
17947        },
17948        cx,
17949    )
17950    .await;
17951
17952    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17953        let go_to_definition = cx
17954            .lsp
17955            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17956                move |params, _| async move {
17957                    if empty_go_to_definition {
17958                        Ok(None)
17959                    } else {
17960                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17961                            uri: params.text_document_position_params.text_document.uri,
17962                            range: lsp::Range::new(
17963                                lsp::Position::new(4, 3),
17964                                lsp::Position::new(4, 6),
17965                            ),
17966                        })))
17967                    }
17968                },
17969            );
17970        let references = cx
17971            .lsp
17972            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17973                Ok(Some(vec![lsp::Location {
17974                    uri: params.text_document_position.text_document.uri,
17975                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17976                }]))
17977            });
17978        (go_to_definition, references)
17979    };
17980
17981    cx.set_state(
17982        &r#"fn one() {
17983            let mut a = ˇtwo();
17984        }
17985
17986        fn two() {}"#
17987            .unindent(),
17988    );
17989    set_up_lsp_handlers(false, &mut cx);
17990    let navigated = cx
17991        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17992        .await
17993        .expect("Failed to navigate to definition");
17994    assert_eq!(
17995        navigated,
17996        Navigated::Yes,
17997        "Should have navigated to definition from the GetDefinition response"
17998    );
17999    cx.assert_editor_state(
18000        &r#"fn one() {
18001            let mut a = two();
18002        }
18003
18004        fn «twoˇ»() {}"#
18005            .unindent(),
18006    );
18007
18008    let editors = cx.update_workspace(|workspace, _, cx| {
18009        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18010    });
18011    cx.update_editor(|_, _, test_editor_cx| {
18012        assert_eq!(
18013            editors.len(),
18014            1,
18015            "Initially, only one, test, editor should be open in the workspace"
18016        );
18017        assert_eq!(
18018            test_editor_cx.entity(),
18019            editors.last().expect("Asserted len is 1").clone()
18020        );
18021    });
18022
18023    set_up_lsp_handlers(true, &mut cx);
18024    let navigated = cx
18025        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18026        .await
18027        .expect("Failed to navigate to lookup references");
18028    assert_eq!(
18029        navigated,
18030        Navigated::Yes,
18031        "Should have navigated to references as a fallback after empty GoToDefinition response"
18032    );
18033    // We should not change the selections in the existing file,
18034    // if opening another milti buffer with the references
18035    cx.assert_editor_state(
18036        &r#"fn one() {
18037            let mut a = two();
18038        }
18039
18040        fn «twoˇ»() {}"#
18041            .unindent(),
18042    );
18043    let editors = cx.update_workspace(|workspace, _, cx| {
18044        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18045    });
18046    cx.update_editor(|_, _, test_editor_cx| {
18047        assert_eq!(
18048            editors.len(),
18049            2,
18050            "After falling back to references search, we open a new editor with the results"
18051        );
18052        let references_fallback_text = editors
18053            .into_iter()
18054            .find(|new_editor| *new_editor != test_editor_cx.entity())
18055            .expect("Should have one non-test editor now")
18056            .read(test_editor_cx)
18057            .text(test_editor_cx);
18058        assert_eq!(
18059            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18060            "Should use the range from the references response and not the GoToDefinition one"
18061        );
18062    });
18063}
18064
18065#[gpui::test]
18066async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18067    init_test(cx, |_| {});
18068    cx.update(|cx| {
18069        let mut editor_settings = EditorSettings::get_global(cx).clone();
18070        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18071        EditorSettings::override_global(editor_settings, cx);
18072    });
18073    let mut cx = EditorLspTestContext::new_rust(
18074        lsp::ServerCapabilities {
18075            definition_provider: Some(lsp::OneOf::Left(true)),
18076            references_provider: Some(lsp::OneOf::Left(true)),
18077            ..lsp::ServerCapabilities::default()
18078        },
18079        cx,
18080    )
18081    .await;
18082    let original_state = r#"fn one() {
18083        let mut a = ˇtwo();
18084    }
18085
18086    fn two() {}"#
18087        .unindent();
18088    cx.set_state(&original_state);
18089
18090    let mut go_to_definition = cx
18091        .lsp
18092        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18093            move |_, _| async move { Ok(None) },
18094        );
18095    let _references = cx
18096        .lsp
18097        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18098            panic!("Should not call for references with no go to definition fallback")
18099        });
18100
18101    let navigated = cx
18102        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18103        .await
18104        .expect("Failed to navigate to lookup references");
18105    go_to_definition
18106        .next()
18107        .await
18108        .expect("Should have called the go_to_definition handler");
18109
18110    assert_eq!(
18111        navigated,
18112        Navigated::No,
18113        "Should have navigated to references as a fallback after empty GoToDefinition response"
18114    );
18115    cx.assert_editor_state(&original_state);
18116    let editors = cx.update_workspace(|workspace, _, cx| {
18117        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18118    });
18119    cx.update_editor(|_, _, _| {
18120        assert_eq!(
18121            editors.len(),
18122            1,
18123            "After unsuccessful fallback, no other editor should have been opened"
18124        );
18125    });
18126}
18127
18128#[gpui::test]
18129async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18130    init_test(cx, |_| {});
18131
18132    let language = Arc::new(Language::new(
18133        LanguageConfig::default(),
18134        Some(tree_sitter_rust::LANGUAGE.into()),
18135    ));
18136
18137    let text = r#"
18138        #[cfg(test)]
18139        mod tests() {
18140            #[test]
18141            fn runnable_1() {
18142                let a = 1;
18143            }
18144
18145            #[test]
18146            fn runnable_2() {
18147                let a = 1;
18148                let b = 2;
18149            }
18150        }
18151    "#
18152    .unindent();
18153
18154    let fs = FakeFs::new(cx.executor());
18155    fs.insert_file("/file.rs", Default::default()).await;
18156
18157    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18159    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18160    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18161    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18162
18163    let editor = cx.new_window_entity(|window, cx| {
18164        Editor::new(
18165            EditorMode::full(),
18166            multi_buffer,
18167            Some(project.clone()),
18168            window,
18169            cx,
18170        )
18171    });
18172
18173    editor.update_in(cx, |editor, window, cx| {
18174        let snapshot = editor.buffer().read(cx).snapshot(cx);
18175        editor.tasks.insert(
18176            (buffer.read(cx).remote_id(), 3),
18177            RunnableTasks {
18178                templates: vec![],
18179                offset: snapshot.anchor_before(43),
18180                column: 0,
18181                extra_variables: HashMap::default(),
18182                context_range: BufferOffset(43)..BufferOffset(85),
18183            },
18184        );
18185        editor.tasks.insert(
18186            (buffer.read(cx).remote_id(), 8),
18187            RunnableTasks {
18188                templates: vec![],
18189                offset: snapshot.anchor_before(86),
18190                column: 0,
18191                extra_variables: HashMap::default(),
18192                context_range: BufferOffset(86)..BufferOffset(191),
18193            },
18194        );
18195
18196        // Test finding task when cursor is inside function body
18197        editor.change_selections(None, window, cx, |s| {
18198            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18199        });
18200        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18201        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18202
18203        // Test finding task when cursor is on function name
18204        editor.change_selections(None, window, cx, |s| {
18205            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18206        });
18207        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18208        assert_eq!(row, 8, "Should find task when cursor is on function name");
18209    });
18210}
18211
18212#[gpui::test]
18213async fn test_folding_buffers(cx: &mut TestAppContext) {
18214    init_test(cx, |_| {});
18215
18216    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18217    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18218    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18219
18220    let fs = FakeFs::new(cx.executor());
18221    fs.insert_tree(
18222        path!("/a"),
18223        json!({
18224            "first.rs": sample_text_1,
18225            "second.rs": sample_text_2,
18226            "third.rs": sample_text_3,
18227        }),
18228    )
18229    .await;
18230    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18231    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18232    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18233    let worktree = project.update(cx, |project, cx| {
18234        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18235        assert_eq!(worktrees.len(), 1);
18236        worktrees.pop().unwrap()
18237    });
18238    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18239
18240    let buffer_1 = project
18241        .update(cx, |project, cx| {
18242            project.open_buffer((worktree_id, "first.rs"), cx)
18243        })
18244        .await
18245        .unwrap();
18246    let buffer_2 = project
18247        .update(cx, |project, cx| {
18248            project.open_buffer((worktree_id, "second.rs"), cx)
18249        })
18250        .await
18251        .unwrap();
18252    let buffer_3 = project
18253        .update(cx, |project, cx| {
18254            project.open_buffer((worktree_id, "third.rs"), cx)
18255        })
18256        .await
18257        .unwrap();
18258
18259    let multi_buffer = cx.new(|cx| {
18260        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18261        multi_buffer.push_excerpts(
18262            buffer_1.clone(),
18263            [
18264                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18265                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18266                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18267            ],
18268            cx,
18269        );
18270        multi_buffer.push_excerpts(
18271            buffer_2.clone(),
18272            [
18273                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18274                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18275                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18276            ],
18277            cx,
18278        );
18279        multi_buffer.push_excerpts(
18280            buffer_3.clone(),
18281            [
18282                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18283                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18284                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18285            ],
18286            cx,
18287        );
18288        multi_buffer
18289    });
18290    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18291        Editor::new(
18292            EditorMode::full(),
18293            multi_buffer.clone(),
18294            Some(project.clone()),
18295            window,
18296            cx,
18297        )
18298    });
18299
18300    assert_eq!(
18301        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18302        "\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",
18303    );
18304
18305    multi_buffer_editor.update(cx, |editor, cx| {
18306        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18307    });
18308    assert_eq!(
18309        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18310        "\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",
18311        "After folding the first buffer, its text should not be displayed"
18312    );
18313
18314    multi_buffer_editor.update(cx, |editor, cx| {
18315        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18316    });
18317    assert_eq!(
18318        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18319        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18320        "After folding the second buffer, its text should not be displayed"
18321    );
18322
18323    multi_buffer_editor.update(cx, |editor, cx| {
18324        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18325    });
18326    assert_eq!(
18327        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18328        "\n\n\n\n\n",
18329        "After folding the third buffer, its text should not be displayed"
18330    );
18331
18332    // Emulate selection inside the fold logic, that should work
18333    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18334        editor
18335            .snapshot(window, cx)
18336            .next_line_boundary(Point::new(0, 4));
18337    });
18338
18339    multi_buffer_editor.update(cx, |editor, cx| {
18340        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18341    });
18342    assert_eq!(
18343        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18344        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18345        "After unfolding the second buffer, its text should be displayed"
18346    );
18347
18348    // Typing inside of buffer 1 causes that buffer to be unfolded.
18349    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18350        assert_eq!(
18351            multi_buffer
18352                .read(cx)
18353                .snapshot(cx)
18354                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18355                .collect::<String>(),
18356            "bbbb"
18357        );
18358        editor.change_selections(None, window, cx, |selections| {
18359            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18360        });
18361        editor.handle_input("B", window, cx);
18362    });
18363
18364    assert_eq!(
18365        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18366        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18367        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18368    );
18369
18370    multi_buffer_editor.update(cx, |editor, cx| {
18371        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18372    });
18373    assert_eq!(
18374        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18375        "\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",
18376        "After unfolding the all buffers, all original text should be displayed"
18377    );
18378}
18379
18380#[gpui::test]
18381async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18382    init_test(cx, |_| {});
18383
18384    let sample_text_1 = "1111\n2222\n3333".to_string();
18385    let sample_text_2 = "4444\n5555\n6666".to_string();
18386    let sample_text_3 = "7777\n8888\n9999".to_string();
18387
18388    let fs = FakeFs::new(cx.executor());
18389    fs.insert_tree(
18390        path!("/a"),
18391        json!({
18392            "first.rs": sample_text_1,
18393            "second.rs": sample_text_2,
18394            "third.rs": sample_text_3,
18395        }),
18396    )
18397    .await;
18398    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18399    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18400    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18401    let worktree = project.update(cx, |project, cx| {
18402        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18403        assert_eq!(worktrees.len(), 1);
18404        worktrees.pop().unwrap()
18405    });
18406    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18407
18408    let buffer_1 = project
18409        .update(cx, |project, cx| {
18410            project.open_buffer((worktree_id, "first.rs"), cx)
18411        })
18412        .await
18413        .unwrap();
18414    let buffer_2 = project
18415        .update(cx, |project, cx| {
18416            project.open_buffer((worktree_id, "second.rs"), cx)
18417        })
18418        .await
18419        .unwrap();
18420    let buffer_3 = project
18421        .update(cx, |project, cx| {
18422            project.open_buffer((worktree_id, "third.rs"), cx)
18423        })
18424        .await
18425        .unwrap();
18426
18427    let multi_buffer = cx.new(|cx| {
18428        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18429        multi_buffer.push_excerpts(
18430            buffer_1.clone(),
18431            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18432            cx,
18433        );
18434        multi_buffer.push_excerpts(
18435            buffer_2.clone(),
18436            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18437            cx,
18438        );
18439        multi_buffer.push_excerpts(
18440            buffer_3.clone(),
18441            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18442            cx,
18443        );
18444        multi_buffer
18445    });
18446
18447    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18448        Editor::new(
18449            EditorMode::full(),
18450            multi_buffer,
18451            Some(project.clone()),
18452            window,
18453            cx,
18454        )
18455    });
18456
18457    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18458    assert_eq!(
18459        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18460        full_text,
18461    );
18462
18463    multi_buffer_editor.update(cx, |editor, cx| {
18464        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18465    });
18466    assert_eq!(
18467        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18468        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18469        "After folding the first buffer, its text should not be displayed"
18470    );
18471
18472    multi_buffer_editor.update(cx, |editor, cx| {
18473        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18474    });
18475
18476    assert_eq!(
18477        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18478        "\n\n\n\n\n\n7777\n8888\n9999",
18479        "After folding the second buffer, its text should not be displayed"
18480    );
18481
18482    multi_buffer_editor.update(cx, |editor, cx| {
18483        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18484    });
18485    assert_eq!(
18486        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18487        "\n\n\n\n\n",
18488        "After folding the third buffer, its text should not be displayed"
18489    );
18490
18491    multi_buffer_editor.update(cx, |editor, cx| {
18492        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18493    });
18494    assert_eq!(
18495        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18496        "\n\n\n\n4444\n5555\n6666\n\n",
18497        "After unfolding the second buffer, its text should be displayed"
18498    );
18499
18500    multi_buffer_editor.update(cx, |editor, cx| {
18501        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18502    });
18503    assert_eq!(
18504        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18505        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18506        "After unfolding the first buffer, its text should be displayed"
18507    );
18508
18509    multi_buffer_editor.update(cx, |editor, cx| {
18510        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18511    });
18512    assert_eq!(
18513        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18514        full_text,
18515        "After unfolding all buffers, all original text should be displayed"
18516    );
18517}
18518
18519#[gpui::test]
18520async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18521    init_test(cx, |_| {});
18522
18523    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18524
18525    let fs = FakeFs::new(cx.executor());
18526    fs.insert_tree(
18527        path!("/a"),
18528        json!({
18529            "main.rs": sample_text,
18530        }),
18531    )
18532    .await;
18533    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18534    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18535    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18536    let worktree = project.update(cx, |project, cx| {
18537        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18538        assert_eq!(worktrees.len(), 1);
18539        worktrees.pop().unwrap()
18540    });
18541    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18542
18543    let buffer_1 = project
18544        .update(cx, |project, cx| {
18545            project.open_buffer((worktree_id, "main.rs"), cx)
18546        })
18547        .await
18548        .unwrap();
18549
18550    let multi_buffer = cx.new(|cx| {
18551        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18552        multi_buffer.push_excerpts(
18553            buffer_1.clone(),
18554            [ExcerptRange::new(
18555                Point::new(0, 0)
18556                    ..Point::new(
18557                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18558                        0,
18559                    ),
18560            )],
18561            cx,
18562        );
18563        multi_buffer
18564    });
18565    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18566        Editor::new(
18567            EditorMode::full(),
18568            multi_buffer,
18569            Some(project.clone()),
18570            window,
18571            cx,
18572        )
18573    });
18574
18575    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18576    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18577        enum TestHighlight {}
18578        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18579        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18580        editor.highlight_text::<TestHighlight>(
18581            vec![highlight_range.clone()],
18582            HighlightStyle::color(Hsla::green()),
18583            cx,
18584        );
18585        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18586    });
18587
18588    let full_text = format!("\n\n{sample_text}");
18589    assert_eq!(
18590        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18591        full_text,
18592    );
18593}
18594
18595#[gpui::test]
18596async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18597    init_test(cx, |_| {});
18598    cx.update(|cx| {
18599        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18600            "keymaps/default-linux.json",
18601            cx,
18602        )
18603        .unwrap();
18604        cx.bind_keys(default_key_bindings);
18605    });
18606
18607    let (editor, cx) = cx.add_window_view(|window, cx| {
18608        let multi_buffer = MultiBuffer::build_multi(
18609            [
18610                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18611                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18612                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18613                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18614            ],
18615            cx,
18616        );
18617        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18618
18619        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18620        // fold all but the second buffer, so that we test navigating between two
18621        // adjacent folded buffers, as well as folded buffers at the start and
18622        // end the multibuffer
18623        editor.fold_buffer(buffer_ids[0], cx);
18624        editor.fold_buffer(buffer_ids[2], cx);
18625        editor.fold_buffer(buffer_ids[3], cx);
18626
18627        editor
18628    });
18629    cx.simulate_resize(size(px(1000.), px(1000.)));
18630
18631    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18632    cx.assert_excerpts_with_selections(indoc! {"
18633        [EXCERPT]
18634        ˇ[FOLDED]
18635        [EXCERPT]
18636        a1
18637        b1
18638        [EXCERPT]
18639        [FOLDED]
18640        [EXCERPT]
18641        [FOLDED]
18642        "
18643    });
18644    cx.simulate_keystroke("down");
18645    cx.assert_excerpts_with_selections(indoc! {"
18646        [EXCERPT]
18647        [FOLDED]
18648        [EXCERPT]
18649        ˇa1
18650        b1
18651        [EXCERPT]
18652        [FOLDED]
18653        [EXCERPT]
18654        [FOLDED]
18655        "
18656    });
18657    cx.simulate_keystroke("down");
18658    cx.assert_excerpts_with_selections(indoc! {"
18659        [EXCERPT]
18660        [FOLDED]
18661        [EXCERPT]
18662        a1
18663        ˇb1
18664        [EXCERPT]
18665        [FOLDED]
18666        [EXCERPT]
18667        [FOLDED]
18668        "
18669    });
18670    cx.simulate_keystroke("down");
18671    cx.assert_excerpts_with_selections(indoc! {"
18672        [EXCERPT]
18673        [FOLDED]
18674        [EXCERPT]
18675        a1
18676        b1
18677        ˇ[EXCERPT]
18678        [FOLDED]
18679        [EXCERPT]
18680        [FOLDED]
18681        "
18682    });
18683    cx.simulate_keystroke("down");
18684    cx.assert_excerpts_with_selections(indoc! {"
18685        [EXCERPT]
18686        [FOLDED]
18687        [EXCERPT]
18688        a1
18689        b1
18690        [EXCERPT]
18691        ˇ[FOLDED]
18692        [EXCERPT]
18693        [FOLDED]
18694        "
18695    });
18696    for _ in 0..5 {
18697        cx.simulate_keystroke("down");
18698        cx.assert_excerpts_with_selections(indoc! {"
18699            [EXCERPT]
18700            [FOLDED]
18701            [EXCERPT]
18702            a1
18703            b1
18704            [EXCERPT]
18705            [FOLDED]
18706            [EXCERPT]
18707            ˇ[FOLDED]
18708            "
18709        });
18710    }
18711
18712    cx.simulate_keystroke("up");
18713    cx.assert_excerpts_with_selections(indoc! {"
18714        [EXCERPT]
18715        [FOLDED]
18716        [EXCERPT]
18717        a1
18718        b1
18719        [EXCERPT]
18720        ˇ[FOLDED]
18721        [EXCERPT]
18722        [FOLDED]
18723        "
18724    });
18725    cx.simulate_keystroke("up");
18726    cx.assert_excerpts_with_selections(indoc! {"
18727        [EXCERPT]
18728        [FOLDED]
18729        [EXCERPT]
18730        a1
18731        b1
18732        ˇ[EXCERPT]
18733        [FOLDED]
18734        [EXCERPT]
18735        [FOLDED]
18736        "
18737    });
18738    cx.simulate_keystroke("up");
18739    cx.assert_excerpts_with_selections(indoc! {"
18740        [EXCERPT]
18741        [FOLDED]
18742        [EXCERPT]
18743        a1
18744        ˇb1
18745        [EXCERPT]
18746        [FOLDED]
18747        [EXCERPT]
18748        [FOLDED]
18749        "
18750    });
18751    cx.simulate_keystroke("up");
18752    cx.assert_excerpts_with_selections(indoc! {"
18753        [EXCERPT]
18754        [FOLDED]
18755        [EXCERPT]
18756        ˇa1
18757        b1
18758        [EXCERPT]
18759        [FOLDED]
18760        [EXCERPT]
18761        [FOLDED]
18762        "
18763    });
18764    for _ in 0..5 {
18765        cx.simulate_keystroke("up");
18766        cx.assert_excerpts_with_selections(indoc! {"
18767            [EXCERPT]
18768            ˇ[FOLDED]
18769            [EXCERPT]
18770            a1
18771            b1
18772            [EXCERPT]
18773            [FOLDED]
18774            [EXCERPT]
18775            [FOLDED]
18776            "
18777        });
18778    }
18779}
18780
18781#[gpui::test]
18782async fn test_inline_completion_text(cx: &mut TestAppContext) {
18783    init_test(cx, |_| {});
18784
18785    // Simple insertion
18786    assert_highlighted_edits(
18787        "Hello, world!",
18788        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18789        true,
18790        cx,
18791        |highlighted_edits, cx| {
18792            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18793            assert_eq!(highlighted_edits.highlights.len(), 1);
18794            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18795            assert_eq!(
18796                highlighted_edits.highlights[0].1.background_color,
18797                Some(cx.theme().status().created_background)
18798            );
18799        },
18800    )
18801    .await;
18802
18803    // Replacement
18804    assert_highlighted_edits(
18805        "This is a test.",
18806        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18807        false,
18808        cx,
18809        |highlighted_edits, cx| {
18810            assert_eq!(highlighted_edits.text, "That is a test.");
18811            assert_eq!(highlighted_edits.highlights.len(), 1);
18812            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18813            assert_eq!(
18814                highlighted_edits.highlights[0].1.background_color,
18815                Some(cx.theme().status().created_background)
18816            );
18817        },
18818    )
18819    .await;
18820
18821    // Multiple edits
18822    assert_highlighted_edits(
18823        "Hello, world!",
18824        vec![
18825            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18826            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18827        ],
18828        false,
18829        cx,
18830        |highlighted_edits, cx| {
18831            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18832            assert_eq!(highlighted_edits.highlights.len(), 2);
18833            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18834            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18835            assert_eq!(
18836                highlighted_edits.highlights[0].1.background_color,
18837                Some(cx.theme().status().created_background)
18838            );
18839            assert_eq!(
18840                highlighted_edits.highlights[1].1.background_color,
18841                Some(cx.theme().status().created_background)
18842            );
18843        },
18844    )
18845    .await;
18846
18847    // Multiple lines with edits
18848    assert_highlighted_edits(
18849        "First line\nSecond line\nThird line\nFourth line",
18850        vec![
18851            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18852            (
18853                Point::new(2, 0)..Point::new(2, 10),
18854                "New third line".to_string(),
18855            ),
18856            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18857        ],
18858        false,
18859        cx,
18860        |highlighted_edits, cx| {
18861            assert_eq!(
18862                highlighted_edits.text,
18863                "Second modified\nNew third line\nFourth updated line"
18864            );
18865            assert_eq!(highlighted_edits.highlights.len(), 3);
18866            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18867            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18868            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18869            for highlight in &highlighted_edits.highlights {
18870                assert_eq!(
18871                    highlight.1.background_color,
18872                    Some(cx.theme().status().created_background)
18873                );
18874            }
18875        },
18876    )
18877    .await;
18878}
18879
18880#[gpui::test]
18881async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18882    init_test(cx, |_| {});
18883
18884    // Deletion
18885    assert_highlighted_edits(
18886        "Hello, world!",
18887        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18888        true,
18889        cx,
18890        |highlighted_edits, cx| {
18891            assert_eq!(highlighted_edits.text, "Hello, world!");
18892            assert_eq!(highlighted_edits.highlights.len(), 1);
18893            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18894            assert_eq!(
18895                highlighted_edits.highlights[0].1.background_color,
18896                Some(cx.theme().status().deleted_background)
18897            );
18898        },
18899    )
18900    .await;
18901
18902    // Insertion
18903    assert_highlighted_edits(
18904        "Hello, world!",
18905        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18906        true,
18907        cx,
18908        |highlighted_edits, cx| {
18909            assert_eq!(highlighted_edits.highlights.len(), 1);
18910            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18911            assert_eq!(
18912                highlighted_edits.highlights[0].1.background_color,
18913                Some(cx.theme().status().created_background)
18914            );
18915        },
18916    )
18917    .await;
18918}
18919
18920async fn assert_highlighted_edits(
18921    text: &str,
18922    edits: Vec<(Range<Point>, String)>,
18923    include_deletions: bool,
18924    cx: &mut TestAppContext,
18925    assertion_fn: impl Fn(HighlightedText, &App),
18926) {
18927    let window = cx.add_window(|window, cx| {
18928        let buffer = MultiBuffer::build_simple(text, cx);
18929        Editor::new(EditorMode::full(), buffer, None, window, cx)
18930    });
18931    let cx = &mut VisualTestContext::from_window(*window, cx);
18932
18933    let (buffer, snapshot) = window
18934        .update(cx, |editor, _window, cx| {
18935            (
18936                editor.buffer().clone(),
18937                editor.buffer().read(cx).snapshot(cx),
18938            )
18939        })
18940        .unwrap();
18941
18942    let edits = edits
18943        .into_iter()
18944        .map(|(range, edit)| {
18945            (
18946                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18947                edit,
18948            )
18949        })
18950        .collect::<Vec<_>>();
18951
18952    let text_anchor_edits = edits
18953        .clone()
18954        .into_iter()
18955        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18956        .collect::<Vec<_>>();
18957
18958    let edit_preview = window
18959        .update(cx, |_, _window, cx| {
18960            buffer
18961                .read(cx)
18962                .as_singleton()
18963                .unwrap()
18964                .read(cx)
18965                .preview_edits(text_anchor_edits.into(), cx)
18966        })
18967        .unwrap()
18968        .await;
18969
18970    cx.update(|_window, cx| {
18971        let highlighted_edits = inline_completion_edit_text(
18972            &snapshot.as_singleton().unwrap().2,
18973            &edits,
18974            &edit_preview,
18975            include_deletions,
18976            cx,
18977        );
18978        assertion_fn(highlighted_edits, cx)
18979    });
18980}
18981
18982#[track_caller]
18983fn assert_breakpoint(
18984    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18985    path: &Arc<Path>,
18986    expected: Vec<(u32, Breakpoint)>,
18987) {
18988    if expected.len() == 0usize {
18989        assert!(!breakpoints.contains_key(path), "{}", path.display());
18990    } else {
18991        let mut breakpoint = breakpoints
18992            .get(path)
18993            .unwrap()
18994            .into_iter()
18995            .map(|breakpoint| {
18996                (
18997                    breakpoint.row,
18998                    Breakpoint {
18999                        message: breakpoint.message.clone(),
19000                        state: breakpoint.state,
19001                        condition: breakpoint.condition.clone(),
19002                        hit_condition: breakpoint.hit_condition.clone(),
19003                    },
19004                )
19005            })
19006            .collect::<Vec<_>>();
19007
19008        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19009
19010        assert_eq!(expected, breakpoint);
19011    }
19012}
19013
19014fn add_log_breakpoint_at_cursor(
19015    editor: &mut Editor,
19016    log_message: &str,
19017    window: &mut Window,
19018    cx: &mut Context<Editor>,
19019) {
19020    let (anchor, bp) = editor
19021        .breakpoints_at_cursors(window, cx)
19022        .first()
19023        .and_then(|(anchor, bp)| {
19024            if let Some(bp) = bp {
19025                Some((*anchor, bp.clone()))
19026            } else {
19027                None
19028            }
19029        })
19030        .unwrap_or_else(|| {
19031            let cursor_position: Point = editor.selections.newest(cx).head();
19032
19033            let breakpoint_position = editor
19034                .snapshot(window, cx)
19035                .display_snapshot
19036                .buffer_snapshot
19037                .anchor_before(Point::new(cursor_position.row, 0));
19038
19039            (breakpoint_position, Breakpoint::new_log(&log_message))
19040        });
19041
19042    editor.edit_breakpoint_at_anchor(
19043        anchor,
19044        bp,
19045        BreakpointEditAction::EditLogMessage(log_message.into()),
19046        cx,
19047    );
19048}
19049
19050#[gpui::test]
19051async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19052    init_test(cx, |_| {});
19053
19054    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19055    let fs = FakeFs::new(cx.executor());
19056    fs.insert_tree(
19057        path!("/a"),
19058        json!({
19059            "main.rs": sample_text,
19060        }),
19061    )
19062    .await;
19063    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19064    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19065    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19066
19067    let fs = FakeFs::new(cx.executor());
19068    fs.insert_tree(
19069        path!("/a"),
19070        json!({
19071            "main.rs": sample_text,
19072        }),
19073    )
19074    .await;
19075    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19076    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19077    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19078    let worktree_id = workspace
19079        .update(cx, |workspace, _window, cx| {
19080            workspace.project().update(cx, |project, cx| {
19081                project.worktrees(cx).next().unwrap().read(cx).id()
19082            })
19083        })
19084        .unwrap();
19085
19086    let buffer = project
19087        .update(cx, |project, cx| {
19088            project.open_buffer((worktree_id, "main.rs"), cx)
19089        })
19090        .await
19091        .unwrap();
19092
19093    let (editor, cx) = cx.add_window_view(|window, cx| {
19094        Editor::new(
19095            EditorMode::full(),
19096            MultiBuffer::build_from_buffer(buffer, cx),
19097            Some(project.clone()),
19098            window,
19099            cx,
19100        )
19101    });
19102
19103    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19104    let abs_path = project.read_with(cx, |project, cx| {
19105        project
19106            .absolute_path(&project_path, cx)
19107            .map(|path_buf| Arc::from(path_buf.to_owned()))
19108            .unwrap()
19109    });
19110
19111    // assert we can add breakpoint on the first line
19112    editor.update_in(cx, |editor, window, cx| {
19113        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19114        editor.move_to_end(&MoveToEnd, window, cx);
19115        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19116    });
19117
19118    let breakpoints = editor.update(cx, |editor, cx| {
19119        editor
19120            .breakpoint_store()
19121            .as_ref()
19122            .unwrap()
19123            .read(cx)
19124            .all_source_breakpoints(cx)
19125            .clone()
19126    });
19127
19128    assert_eq!(1, breakpoints.len());
19129    assert_breakpoint(
19130        &breakpoints,
19131        &abs_path,
19132        vec![
19133            (0, Breakpoint::new_standard()),
19134            (3, Breakpoint::new_standard()),
19135        ],
19136    );
19137
19138    editor.update_in(cx, |editor, window, cx| {
19139        editor.move_to_beginning(&MoveToBeginning, window, cx);
19140        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19141    });
19142
19143    let breakpoints = editor.update(cx, |editor, cx| {
19144        editor
19145            .breakpoint_store()
19146            .as_ref()
19147            .unwrap()
19148            .read(cx)
19149            .all_source_breakpoints(cx)
19150            .clone()
19151    });
19152
19153    assert_eq!(1, breakpoints.len());
19154    assert_breakpoint(
19155        &breakpoints,
19156        &abs_path,
19157        vec![(3, Breakpoint::new_standard())],
19158    );
19159
19160    editor.update_in(cx, |editor, window, cx| {
19161        editor.move_to_end(&MoveToEnd, window, cx);
19162        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19163    });
19164
19165    let breakpoints = editor.update(cx, |editor, cx| {
19166        editor
19167            .breakpoint_store()
19168            .as_ref()
19169            .unwrap()
19170            .read(cx)
19171            .all_source_breakpoints(cx)
19172            .clone()
19173    });
19174
19175    assert_eq!(0, breakpoints.len());
19176    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19177}
19178
19179#[gpui::test]
19180async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19181    init_test(cx, |_| {});
19182
19183    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19184
19185    let fs = FakeFs::new(cx.executor());
19186    fs.insert_tree(
19187        path!("/a"),
19188        json!({
19189            "main.rs": sample_text,
19190        }),
19191    )
19192    .await;
19193    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19194    let (workspace, cx) =
19195        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19196
19197    let worktree_id = workspace.update(cx, |workspace, cx| {
19198        workspace.project().update(cx, |project, cx| {
19199            project.worktrees(cx).next().unwrap().read(cx).id()
19200        })
19201    });
19202
19203    let buffer = project
19204        .update(cx, |project, cx| {
19205            project.open_buffer((worktree_id, "main.rs"), cx)
19206        })
19207        .await
19208        .unwrap();
19209
19210    let (editor, cx) = cx.add_window_view(|window, cx| {
19211        Editor::new(
19212            EditorMode::full(),
19213            MultiBuffer::build_from_buffer(buffer, cx),
19214            Some(project.clone()),
19215            window,
19216            cx,
19217        )
19218    });
19219
19220    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19221    let abs_path = project.read_with(cx, |project, cx| {
19222        project
19223            .absolute_path(&project_path, cx)
19224            .map(|path_buf| Arc::from(path_buf.to_owned()))
19225            .unwrap()
19226    });
19227
19228    editor.update_in(cx, |editor, window, cx| {
19229        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19230    });
19231
19232    let breakpoints = editor.update(cx, |editor, cx| {
19233        editor
19234            .breakpoint_store()
19235            .as_ref()
19236            .unwrap()
19237            .read(cx)
19238            .all_source_breakpoints(cx)
19239            .clone()
19240    });
19241
19242    assert_breakpoint(
19243        &breakpoints,
19244        &abs_path,
19245        vec![(0, Breakpoint::new_log("hello world"))],
19246    );
19247
19248    // Removing a log message from a log breakpoint should remove it
19249    editor.update_in(cx, |editor, window, cx| {
19250        add_log_breakpoint_at_cursor(editor, "", window, cx);
19251    });
19252
19253    let breakpoints = editor.update(cx, |editor, cx| {
19254        editor
19255            .breakpoint_store()
19256            .as_ref()
19257            .unwrap()
19258            .read(cx)
19259            .all_source_breakpoints(cx)
19260            .clone()
19261    });
19262
19263    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19264
19265    editor.update_in(cx, |editor, window, cx| {
19266        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19267        editor.move_to_end(&MoveToEnd, window, cx);
19268        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19269        // Not adding a log message to a standard breakpoint shouldn't remove it
19270        add_log_breakpoint_at_cursor(editor, "", window, cx);
19271    });
19272
19273    let breakpoints = editor.update(cx, |editor, cx| {
19274        editor
19275            .breakpoint_store()
19276            .as_ref()
19277            .unwrap()
19278            .read(cx)
19279            .all_source_breakpoints(cx)
19280            .clone()
19281    });
19282
19283    assert_breakpoint(
19284        &breakpoints,
19285        &abs_path,
19286        vec![
19287            (0, Breakpoint::new_standard()),
19288            (3, Breakpoint::new_standard()),
19289        ],
19290    );
19291
19292    editor.update_in(cx, |editor, window, cx| {
19293        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19294    });
19295
19296    let breakpoints = editor.update(cx, |editor, cx| {
19297        editor
19298            .breakpoint_store()
19299            .as_ref()
19300            .unwrap()
19301            .read(cx)
19302            .all_source_breakpoints(cx)
19303            .clone()
19304    });
19305
19306    assert_breakpoint(
19307        &breakpoints,
19308        &abs_path,
19309        vec![
19310            (0, Breakpoint::new_standard()),
19311            (3, Breakpoint::new_log("hello world")),
19312        ],
19313    );
19314
19315    editor.update_in(cx, |editor, window, cx| {
19316        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19317    });
19318
19319    let breakpoints = editor.update(cx, |editor, cx| {
19320        editor
19321            .breakpoint_store()
19322            .as_ref()
19323            .unwrap()
19324            .read(cx)
19325            .all_source_breakpoints(cx)
19326            .clone()
19327    });
19328
19329    assert_breakpoint(
19330        &breakpoints,
19331        &abs_path,
19332        vec![
19333            (0, Breakpoint::new_standard()),
19334            (3, Breakpoint::new_log("hello Earth!!")),
19335        ],
19336    );
19337}
19338
19339/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19340/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19341/// or when breakpoints were placed out of order. This tests for a regression too
19342#[gpui::test]
19343async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19344    init_test(cx, |_| {});
19345
19346    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19347    let fs = FakeFs::new(cx.executor());
19348    fs.insert_tree(
19349        path!("/a"),
19350        json!({
19351            "main.rs": sample_text,
19352        }),
19353    )
19354    .await;
19355    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19356    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19357    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19358
19359    let fs = FakeFs::new(cx.executor());
19360    fs.insert_tree(
19361        path!("/a"),
19362        json!({
19363            "main.rs": sample_text,
19364        }),
19365    )
19366    .await;
19367    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19368    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19369    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19370    let worktree_id = workspace
19371        .update(cx, |workspace, _window, cx| {
19372            workspace.project().update(cx, |project, cx| {
19373                project.worktrees(cx).next().unwrap().read(cx).id()
19374            })
19375        })
19376        .unwrap();
19377
19378    let buffer = project
19379        .update(cx, |project, cx| {
19380            project.open_buffer((worktree_id, "main.rs"), cx)
19381        })
19382        .await
19383        .unwrap();
19384
19385    let (editor, cx) = cx.add_window_view(|window, cx| {
19386        Editor::new(
19387            EditorMode::full(),
19388            MultiBuffer::build_from_buffer(buffer, cx),
19389            Some(project.clone()),
19390            window,
19391            cx,
19392        )
19393    });
19394
19395    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19396    let abs_path = project.read_with(cx, |project, cx| {
19397        project
19398            .absolute_path(&project_path, cx)
19399            .map(|path_buf| Arc::from(path_buf.to_owned()))
19400            .unwrap()
19401    });
19402
19403    // assert we can add breakpoint on the first line
19404    editor.update_in(cx, |editor, window, cx| {
19405        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19406        editor.move_to_end(&MoveToEnd, window, cx);
19407        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19408        editor.move_up(&MoveUp, window, cx);
19409        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19410    });
19411
19412    let breakpoints = editor.update(cx, |editor, cx| {
19413        editor
19414            .breakpoint_store()
19415            .as_ref()
19416            .unwrap()
19417            .read(cx)
19418            .all_source_breakpoints(cx)
19419            .clone()
19420    });
19421
19422    assert_eq!(1, breakpoints.len());
19423    assert_breakpoint(
19424        &breakpoints,
19425        &abs_path,
19426        vec![
19427            (0, Breakpoint::new_standard()),
19428            (2, Breakpoint::new_standard()),
19429            (3, Breakpoint::new_standard()),
19430        ],
19431    );
19432
19433    editor.update_in(cx, |editor, window, cx| {
19434        editor.move_to_beginning(&MoveToBeginning, window, cx);
19435        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19436        editor.move_to_end(&MoveToEnd, window, cx);
19437        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19438        // Disabling a breakpoint that doesn't exist should do nothing
19439        editor.move_up(&MoveUp, window, cx);
19440        editor.move_up(&MoveUp, window, cx);
19441        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19442    });
19443
19444    let breakpoints = editor.update(cx, |editor, cx| {
19445        editor
19446            .breakpoint_store()
19447            .as_ref()
19448            .unwrap()
19449            .read(cx)
19450            .all_source_breakpoints(cx)
19451            .clone()
19452    });
19453
19454    let disable_breakpoint = {
19455        let mut bp = Breakpoint::new_standard();
19456        bp.state = BreakpointState::Disabled;
19457        bp
19458    };
19459
19460    assert_eq!(1, breakpoints.len());
19461    assert_breakpoint(
19462        &breakpoints,
19463        &abs_path,
19464        vec![
19465            (0, disable_breakpoint.clone()),
19466            (2, Breakpoint::new_standard()),
19467            (3, disable_breakpoint.clone()),
19468        ],
19469    );
19470
19471    editor.update_in(cx, |editor, window, cx| {
19472        editor.move_to_beginning(&MoveToBeginning, window, cx);
19473        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19474        editor.move_to_end(&MoveToEnd, window, cx);
19475        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19476        editor.move_up(&MoveUp, window, cx);
19477        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19478    });
19479
19480    let breakpoints = editor.update(cx, |editor, cx| {
19481        editor
19482            .breakpoint_store()
19483            .as_ref()
19484            .unwrap()
19485            .read(cx)
19486            .all_source_breakpoints(cx)
19487            .clone()
19488    });
19489
19490    assert_eq!(1, breakpoints.len());
19491    assert_breakpoint(
19492        &breakpoints,
19493        &abs_path,
19494        vec![
19495            (0, Breakpoint::new_standard()),
19496            (2, disable_breakpoint),
19497            (3, Breakpoint::new_standard()),
19498        ],
19499    );
19500}
19501
19502#[gpui::test]
19503async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19504    init_test(cx, |_| {});
19505    let capabilities = lsp::ServerCapabilities {
19506        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19507            prepare_provider: Some(true),
19508            work_done_progress_options: Default::default(),
19509        })),
19510        ..Default::default()
19511    };
19512    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19513
19514    cx.set_state(indoc! {"
19515        struct Fˇoo {}
19516    "});
19517
19518    cx.update_editor(|editor, _, cx| {
19519        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19520        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19521        editor.highlight_background::<DocumentHighlightRead>(
19522            &[highlight_range],
19523            |c| c.editor_document_highlight_read_background,
19524            cx,
19525        );
19526    });
19527
19528    let mut prepare_rename_handler = cx
19529        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19530            move |_, _, _| async move {
19531                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19532                    start: lsp::Position {
19533                        line: 0,
19534                        character: 7,
19535                    },
19536                    end: lsp::Position {
19537                        line: 0,
19538                        character: 10,
19539                    },
19540                })))
19541            },
19542        );
19543    let prepare_rename_task = cx
19544        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19545        .expect("Prepare rename was not started");
19546    prepare_rename_handler.next().await.unwrap();
19547    prepare_rename_task.await.expect("Prepare rename failed");
19548
19549    let mut rename_handler =
19550        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19551            let edit = lsp::TextEdit {
19552                range: lsp::Range {
19553                    start: lsp::Position {
19554                        line: 0,
19555                        character: 7,
19556                    },
19557                    end: lsp::Position {
19558                        line: 0,
19559                        character: 10,
19560                    },
19561                },
19562                new_text: "FooRenamed".to_string(),
19563            };
19564            Ok(Some(lsp::WorkspaceEdit::new(
19565                // Specify the same edit twice
19566                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19567            )))
19568        });
19569    let rename_task = cx
19570        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19571        .expect("Confirm rename was not started");
19572    rename_handler.next().await.unwrap();
19573    rename_task.await.expect("Confirm rename failed");
19574    cx.run_until_parked();
19575
19576    // Despite two edits, only one is actually applied as those are identical
19577    cx.assert_editor_state(indoc! {"
19578        struct FooRenamedˇ {}
19579    "});
19580}
19581
19582#[gpui::test]
19583async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19584    init_test(cx, |_| {});
19585    // These capabilities indicate that the server does not support prepare rename.
19586    let capabilities = lsp::ServerCapabilities {
19587        rename_provider: Some(lsp::OneOf::Left(true)),
19588        ..Default::default()
19589    };
19590    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19591
19592    cx.set_state(indoc! {"
19593        struct Fˇoo {}
19594    "});
19595
19596    cx.update_editor(|editor, _window, cx| {
19597        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19598        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19599        editor.highlight_background::<DocumentHighlightRead>(
19600            &[highlight_range],
19601            |c| c.editor_document_highlight_read_background,
19602            cx,
19603        );
19604    });
19605
19606    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19607        .expect("Prepare rename was not started")
19608        .await
19609        .expect("Prepare rename failed");
19610
19611    let mut rename_handler =
19612        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19613            let edit = lsp::TextEdit {
19614                range: lsp::Range {
19615                    start: lsp::Position {
19616                        line: 0,
19617                        character: 7,
19618                    },
19619                    end: lsp::Position {
19620                        line: 0,
19621                        character: 10,
19622                    },
19623                },
19624                new_text: "FooRenamed".to_string(),
19625            };
19626            Ok(Some(lsp::WorkspaceEdit::new(
19627                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19628            )))
19629        });
19630    let rename_task = cx
19631        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19632        .expect("Confirm rename was not started");
19633    rename_handler.next().await.unwrap();
19634    rename_task.await.expect("Confirm rename failed");
19635    cx.run_until_parked();
19636
19637    // Correct range is renamed, as `surrounding_word` is used to find it.
19638    cx.assert_editor_state(indoc! {"
19639        struct FooRenamedˇ {}
19640    "});
19641}
19642
19643#[gpui::test]
19644async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19645    init_test(cx, |_| {});
19646    let mut cx = EditorTestContext::new(cx).await;
19647
19648    let language = Arc::new(
19649        Language::new(
19650            LanguageConfig::default(),
19651            Some(tree_sitter_html::LANGUAGE.into()),
19652        )
19653        .with_brackets_query(
19654            r#"
19655            ("<" @open "/>" @close)
19656            ("</" @open ">" @close)
19657            ("<" @open ">" @close)
19658            ("\"" @open "\"" @close)
19659            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19660        "#,
19661        )
19662        .unwrap(),
19663    );
19664    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19665
19666    cx.set_state(indoc! {"
19667        <span>ˇ</span>
19668    "});
19669    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19670    cx.assert_editor_state(indoc! {"
19671        <span>
19672        ˇ
19673        </span>
19674    "});
19675
19676    cx.set_state(indoc! {"
19677        <span><span></span>ˇ</span>
19678    "});
19679    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19680    cx.assert_editor_state(indoc! {"
19681        <span><span></span>
19682        ˇ</span>
19683    "});
19684
19685    cx.set_state(indoc! {"
19686        <span>ˇ
19687        </span>
19688    "});
19689    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19690    cx.assert_editor_state(indoc! {"
19691        <span>
19692        ˇ
19693        </span>
19694    "});
19695}
19696
19697#[gpui::test(iterations = 10)]
19698async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19699    init_test(cx, |_| {});
19700
19701    let fs = FakeFs::new(cx.executor());
19702    fs.insert_tree(
19703        path!("/dir"),
19704        json!({
19705            "a.ts": "a",
19706        }),
19707    )
19708    .await;
19709
19710    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19713
19714    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19715    language_registry.add(Arc::new(Language::new(
19716        LanguageConfig {
19717            name: "TypeScript".into(),
19718            matcher: LanguageMatcher {
19719                path_suffixes: vec!["ts".to_string()],
19720                ..Default::default()
19721            },
19722            ..Default::default()
19723        },
19724        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19725    )));
19726    let mut fake_language_servers = language_registry.register_fake_lsp(
19727        "TypeScript",
19728        FakeLspAdapter {
19729            capabilities: lsp::ServerCapabilities {
19730                code_lens_provider: Some(lsp::CodeLensOptions {
19731                    resolve_provider: Some(true),
19732                }),
19733                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19734                    commands: vec!["_the/command".to_string()],
19735                    ..lsp::ExecuteCommandOptions::default()
19736                }),
19737                ..lsp::ServerCapabilities::default()
19738            },
19739            ..FakeLspAdapter::default()
19740        },
19741    );
19742
19743    let (buffer, _handle) = project
19744        .update(cx, |p, cx| {
19745            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19746        })
19747        .await
19748        .unwrap();
19749    cx.executor().run_until_parked();
19750
19751    let fake_server = fake_language_servers.next().await.unwrap();
19752
19753    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19754    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19755    drop(buffer_snapshot);
19756    let actions = cx
19757        .update_window(*workspace, |_, window, cx| {
19758            project.code_actions(&buffer, anchor..anchor, window, cx)
19759        })
19760        .unwrap();
19761
19762    fake_server
19763        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19764            Ok(Some(vec![
19765                lsp::CodeLens {
19766                    range: lsp::Range::default(),
19767                    command: Some(lsp::Command {
19768                        title: "Code lens command".to_owned(),
19769                        command: "_the/command".to_owned(),
19770                        arguments: None,
19771                    }),
19772                    data: None,
19773                },
19774                lsp::CodeLens {
19775                    range: lsp::Range::default(),
19776                    command: Some(lsp::Command {
19777                        title: "Command not in capabilities".to_owned(),
19778                        command: "not in capabilities".to_owned(),
19779                        arguments: None,
19780                    }),
19781                    data: None,
19782                },
19783                lsp::CodeLens {
19784                    range: lsp::Range {
19785                        start: lsp::Position {
19786                            line: 1,
19787                            character: 1,
19788                        },
19789                        end: lsp::Position {
19790                            line: 1,
19791                            character: 1,
19792                        },
19793                    },
19794                    command: Some(lsp::Command {
19795                        title: "Command not in range".to_owned(),
19796                        command: "_the/command".to_owned(),
19797                        arguments: None,
19798                    }),
19799                    data: None,
19800                },
19801            ]))
19802        })
19803        .next()
19804        .await;
19805
19806    let actions = actions.await.unwrap();
19807    assert_eq!(
19808        actions.len(),
19809        1,
19810        "Should have only one valid action for the 0..0 range"
19811    );
19812    let action = actions[0].clone();
19813    let apply = project.update(cx, |project, cx| {
19814        project.apply_code_action(buffer.clone(), action, true, cx)
19815    });
19816
19817    // Resolving the code action does not populate its edits. In absence of
19818    // edits, we must execute the given command.
19819    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19820        |mut lens, _| async move {
19821            let lens_command = lens.command.as_mut().expect("should have a command");
19822            assert_eq!(lens_command.title, "Code lens command");
19823            lens_command.arguments = Some(vec![json!("the-argument")]);
19824            Ok(lens)
19825        },
19826    );
19827
19828    // While executing the command, the language server sends the editor
19829    // a `workspaceEdit` request.
19830    fake_server
19831        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19832            let fake = fake_server.clone();
19833            move |params, _| {
19834                assert_eq!(params.command, "_the/command");
19835                let fake = fake.clone();
19836                async move {
19837                    fake.server
19838                        .request::<lsp::request::ApplyWorkspaceEdit>(
19839                            lsp::ApplyWorkspaceEditParams {
19840                                label: None,
19841                                edit: lsp::WorkspaceEdit {
19842                                    changes: Some(
19843                                        [(
19844                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19845                                            vec![lsp::TextEdit {
19846                                                range: lsp::Range::new(
19847                                                    lsp::Position::new(0, 0),
19848                                                    lsp::Position::new(0, 0),
19849                                                ),
19850                                                new_text: "X".into(),
19851                                            }],
19852                                        )]
19853                                        .into_iter()
19854                                        .collect(),
19855                                    ),
19856                                    ..Default::default()
19857                                },
19858                            },
19859                        )
19860                        .await
19861                        .into_response()
19862                        .unwrap();
19863                    Ok(Some(json!(null)))
19864                }
19865            }
19866        })
19867        .next()
19868        .await;
19869
19870    // Applying the code lens command returns a project transaction containing the edits
19871    // sent by the language server in its `workspaceEdit` request.
19872    let transaction = apply.await.unwrap();
19873    assert!(transaction.0.contains_key(&buffer));
19874    buffer.update(cx, |buffer, cx| {
19875        assert_eq!(buffer.text(), "Xa");
19876        buffer.undo(cx);
19877        assert_eq!(buffer.text(), "a");
19878    });
19879}
19880
19881#[gpui::test]
19882async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19883    init_test(cx, |_| {});
19884
19885    let fs = FakeFs::new(cx.executor());
19886    let main_text = r#"fn main() {
19887println!("1");
19888println!("2");
19889println!("3");
19890println!("4");
19891println!("5");
19892}"#;
19893    let lib_text = "mod foo {}";
19894    fs.insert_tree(
19895        path!("/a"),
19896        json!({
19897            "lib.rs": lib_text,
19898            "main.rs": main_text,
19899        }),
19900    )
19901    .await;
19902
19903    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19904    let (workspace, cx) =
19905        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19906    let worktree_id = workspace.update(cx, |workspace, cx| {
19907        workspace.project().update(cx, |project, cx| {
19908            project.worktrees(cx).next().unwrap().read(cx).id()
19909        })
19910    });
19911
19912    let expected_ranges = vec![
19913        Point::new(0, 0)..Point::new(0, 0),
19914        Point::new(1, 0)..Point::new(1, 1),
19915        Point::new(2, 0)..Point::new(2, 2),
19916        Point::new(3, 0)..Point::new(3, 3),
19917    ];
19918
19919    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19920    let editor_1 = workspace
19921        .update_in(cx, |workspace, window, cx| {
19922            workspace.open_path(
19923                (worktree_id, "main.rs"),
19924                Some(pane_1.downgrade()),
19925                true,
19926                window,
19927                cx,
19928            )
19929        })
19930        .unwrap()
19931        .await
19932        .downcast::<Editor>()
19933        .unwrap();
19934    pane_1.update(cx, |pane, cx| {
19935        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19936        open_editor.update(cx, |editor, cx| {
19937            assert_eq!(
19938                editor.display_text(cx),
19939                main_text,
19940                "Original main.rs text on initial open",
19941            );
19942            assert_eq!(
19943                editor
19944                    .selections
19945                    .all::<Point>(cx)
19946                    .into_iter()
19947                    .map(|s| s.range())
19948                    .collect::<Vec<_>>(),
19949                vec![Point::zero()..Point::zero()],
19950                "Default selections on initial open",
19951            );
19952        })
19953    });
19954    editor_1.update_in(cx, |editor, window, cx| {
19955        editor.change_selections(None, window, cx, |s| {
19956            s.select_ranges(expected_ranges.clone());
19957        });
19958    });
19959
19960    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19961        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19962    });
19963    let editor_2 = workspace
19964        .update_in(cx, |workspace, window, cx| {
19965            workspace.open_path(
19966                (worktree_id, "main.rs"),
19967                Some(pane_2.downgrade()),
19968                true,
19969                window,
19970                cx,
19971            )
19972        })
19973        .unwrap()
19974        .await
19975        .downcast::<Editor>()
19976        .unwrap();
19977    pane_2.update(cx, |pane, cx| {
19978        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19979        open_editor.update(cx, |editor, cx| {
19980            assert_eq!(
19981                editor.display_text(cx),
19982                main_text,
19983                "Original main.rs text on initial open in another panel",
19984            );
19985            assert_eq!(
19986                editor
19987                    .selections
19988                    .all::<Point>(cx)
19989                    .into_iter()
19990                    .map(|s| s.range())
19991                    .collect::<Vec<_>>(),
19992                vec![Point::zero()..Point::zero()],
19993                "Default selections on initial open in another panel",
19994            );
19995        })
19996    });
19997
19998    editor_2.update_in(cx, |editor, window, cx| {
19999        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20000    });
20001
20002    let _other_editor_1 = workspace
20003        .update_in(cx, |workspace, window, cx| {
20004            workspace.open_path(
20005                (worktree_id, "lib.rs"),
20006                Some(pane_1.downgrade()),
20007                true,
20008                window,
20009                cx,
20010            )
20011        })
20012        .unwrap()
20013        .await
20014        .downcast::<Editor>()
20015        .unwrap();
20016    pane_1
20017        .update_in(cx, |pane, window, cx| {
20018            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20019                .unwrap()
20020        })
20021        .await
20022        .unwrap();
20023    drop(editor_1);
20024    pane_1.update(cx, |pane, cx| {
20025        pane.active_item()
20026            .unwrap()
20027            .downcast::<Editor>()
20028            .unwrap()
20029            .update(cx, |editor, cx| {
20030                assert_eq!(
20031                    editor.display_text(cx),
20032                    lib_text,
20033                    "Other file should be open and active",
20034                );
20035            });
20036        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20037    });
20038
20039    let _other_editor_2 = workspace
20040        .update_in(cx, |workspace, window, cx| {
20041            workspace.open_path(
20042                (worktree_id, "lib.rs"),
20043                Some(pane_2.downgrade()),
20044                true,
20045                window,
20046                cx,
20047            )
20048        })
20049        .unwrap()
20050        .await
20051        .downcast::<Editor>()
20052        .unwrap();
20053    pane_2
20054        .update_in(cx, |pane, window, cx| {
20055            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20056                .unwrap()
20057        })
20058        .await
20059        .unwrap();
20060    drop(editor_2);
20061    pane_2.update(cx, |pane, cx| {
20062        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20063        open_editor.update(cx, |editor, cx| {
20064            assert_eq!(
20065                editor.display_text(cx),
20066                lib_text,
20067                "Other file should be open and active in another panel too",
20068            );
20069        });
20070        assert_eq!(
20071            pane.items().count(),
20072            1,
20073            "No other editors should be open in another pane",
20074        );
20075    });
20076
20077    let _editor_1_reopened = workspace
20078        .update_in(cx, |workspace, window, cx| {
20079            workspace.open_path(
20080                (worktree_id, "main.rs"),
20081                Some(pane_1.downgrade()),
20082                true,
20083                window,
20084                cx,
20085            )
20086        })
20087        .unwrap()
20088        .await
20089        .downcast::<Editor>()
20090        .unwrap();
20091    let _editor_2_reopened = workspace
20092        .update_in(cx, |workspace, window, cx| {
20093            workspace.open_path(
20094                (worktree_id, "main.rs"),
20095                Some(pane_2.downgrade()),
20096                true,
20097                window,
20098                cx,
20099            )
20100        })
20101        .unwrap()
20102        .await
20103        .downcast::<Editor>()
20104        .unwrap();
20105    pane_1.update(cx, |pane, cx| {
20106        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20107        open_editor.update(cx, |editor, cx| {
20108            assert_eq!(
20109                editor.display_text(cx),
20110                main_text,
20111                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20112            );
20113            assert_eq!(
20114                editor
20115                    .selections
20116                    .all::<Point>(cx)
20117                    .into_iter()
20118                    .map(|s| s.range())
20119                    .collect::<Vec<_>>(),
20120                expected_ranges,
20121                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20122            );
20123        })
20124    });
20125    pane_2.update(cx, |pane, cx| {
20126        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20127        open_editor.update(cx, |editor, cx| {
20128            assert_eq!(
20129                editor.display_text(cx),
20130                r#"fn main() {
20131⋯rintln!("1");
20132⋯intln!("2");
20133⋯ntln!("3");
20134println!("4");
20135println!("5");
20136}"#,
20137                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20138            );
20139            assert_eq!(
20140                editor
20141                    .selections
20142                    .all::<Point>(cx)
20143                    .into_iter()
20144                    .map(|s| s.range())
20145                    .collect::<Vec<_>>(),
20146                vec![Point::zero()..Point::zero()],
20147                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20148            );
20149        })
20150    });
20151}
20152
20153#[gpui::test]
20154async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20155    init_test(cx, |_| {});
20156
20157    let fs = FakeFs::new(cx.executor());
20158    let main_text = r#"fn main() {
20159println!("1");
20160println!("2");
20161println!("3");
20162println!("4");
20163println!("5");
20164}"#;
20165    let lib_text = "mod foo {}";
20166    fs.insert_tree(
20167        path!("/a"),
20168        json!({
20169            "lib.rs": lib_text,
20170            "main.rs": main_text,
20171        }),
20172    )
20173    .await;
20174
20175    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20176    let (workspace, cx) =
20177        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20178    let worktree_id = workspace.update(cx, |workspace, cx| {
20179        workspace.project().update(cx, |project, cx| {
20180            project.worktrees(cx).next().unwrap().read(cx).id()
20181        })
20182    });
20183
20184    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20185    let editor = workspace
20186        .update_in(cx, |workspace, window, cx| {
20187            workspace.open_path(
20188                (worktree_id, "main.rs"),
20189                Some(pane.downgrade()),
20190                true,
20191                window,
20192                cx,
20193            )
20194        })
20195        .unwrap()
20196        .await
20197        .downcast::<Editor>()
20198        .unwrap();
20199    pane.update(cx, |pane, cx| {
20200        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20201        open_editor.update(cx, |editor, cx| {
20202            assert_eq!(
20203                editor.display_text(cx),
20204                main_text,
20205                "Original main.rs text on initial open",
20206            );
20207        })
20208    });
20209    editor.update_in(cx, |editor, window, cx| {
20210        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20211    });
20212
20213    cx.update_global(|store: &mut SettingsStore, cx| {
20214        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20215            s.restore_on_file_reopen = Some(false);
20216        });
20217    });
20218    editor.update_in(cx, |editor, window, cx| {
20219        editor.fold_ranges(
20220            vec![
20221                Point::new(1, 0)..Point::new(1, 1),
20222                Point::new(2, 0)..Point::new(2, 2),
20223                Point::new(3, 0)..Point::new(3, 3),
20224            ],
20225            false,
20226            window,
20227            cx,
20228        );
20229    });
20230    pane.update_in(cx, |pane, window, cx| {
20231        pane.close_all_items(&CloseAllItems::default(), window, cx)
20232            .unwrap()
20233    })
20234    .await
20235    .unwrap();
20236    pane.update(cx, |pane, _| {
20237        assert!(pane.active_item().is_none());
20238    });
20239    cx.update_global(|store: &mut SettingsStore, cx| {
20240        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20241            s.restore_on_file_reopen = Some(true);
20242        });
20243    });
20244
20245    let _editor_reopened = workspace
20246        .update_in(cx, |workspace, window, cx| {
20247            workspace.open_path(
20248                (worktree_id, "main.rs"),
20249                Some(pane.downgrade()),
20250                true,
20251                window,
20252                cx,
20253            )
20254        })
20255        .unwrap()
20256        .await
20257        .downcast::<Editor>()
20258        .unwrap();
20259    pane.update(cx, |pane, cx| {
20260        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20261        open_editor.update(cx, |editor, cx| {
20262            assert_eq!(
20263                editor.display_text(cx),
20264                main_text,
20265                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20266            );
20267        })
20268    });
20269}
20270
20271#[gpui::test]
20272async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20273    struct EmptyModalView {
20274        focus_handle: gpui::FocusHandle,
20275    }
20276    impl EventEmitter<DismissEvent> for EmptyModalView {}
20277    impl Render for EmptyModalView {
20278        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20279            div()
20280        }
20281    }
20282    impl Focusable for EmptyModalView {
20283        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20284            self.focus_handle.clone()
20285        }
20286    }
20287    impl workspace::ModalView for EmptyModalView {}
20288    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20289        EmptyModalView {
20290            focus_handle: cx.focus_handle(),
20291        }
20292    }
20293
20294    init_test(cx, |_| {});
20295
20296    let fs = FakeFs::new(cx.executor());
20297    let project = Project::test(fs, [], cx).await;
20298    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20299    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20300    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20301    let editor = cx.new_window_entity(|window, cx| {
20302        Editor::new(
20303            EditorMode::full(),
20304            buffer,
20305            Some(project.clone()),
20306            window,
20307            cx,
20308        )
20309    });
20310    workspace
20311        .update(cx, |workspace, window, cx| {
20312            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20313        })
20314        .unwrap();
20315    editor.update_in(cx, |editor, window, cx| {
20316        editor.open_context_menu(&OpenContextMenu, window, cx);
20317        assert!(editor.mouse_context_menu.is_some());
20318    });
20319    workspace
20320        .update(cx, |workspace, window, cx| {
20321            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20322        })
20323        .unwrap();
20324    cx.read(|cx| {
20325        assert!(editor.read(cx).mouse_context_menu.is_none());
20326    });
20327}
20328
20329#[gpui::test]
20330async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20331    init_test(cx, |_| {});
20332
20333    let fs = FakeFs::new(cx.executor());
20334    fs.insert_file(path!("/file.html"), Default::default())
20335        .await;
20336
20337    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20338
20339    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20340    let html_language = Arc::new(Language::new(
20341        LanguageConfig {
20342            name: "HTML".into(),
20343            matcher: LanguageMatcher {
20344                path_suffixes: vec!["html".to_string()],
20345                ..LanguageMatcher::default()
20346            },
20347            brackets: BracketPairConfig {
20348                pairs: vec![BracketPair {
20349                    start: "<".into(),
20350                    end: ">".into(),
20351                    close: true,
20352                    ..Default::default()
20353                }],
20354                ..Default::default()
20355            },
20356            ..Default::default()
20357        },
20358        Some(tree_sitter_html::LANGUAGE.into()),
20359    ));
20360    language_registry.add(html_language);
20361    let mut fake_servers = language_registry.register_fake_lsp(
20362        "HTML",
20363        FakeLspAdapter {
20364            capabilities: lsp::ServerCapabilities {
20365                completion_provider: Some(lsp::CompletionOptions {
20366                    resolve_provider: Some(true),
20367                    ..Default::default()
20368                }),
20369                ..Default::default()
20370            },
20371            ..Default::default()
20372        },
20373    );
20374
20375    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20376    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20377
20378    let worktree_id = workspace
20379        .update(cx, |workspace, _window, cx| {
20380            workspace.project().update(cx, |project, cx| {
20381                project.worktrees(cx).next().unwrap().read(cx).id()
20382            })
20383        })
20384        .unwrap();
20385    project
20386        .update(cx, |project, cx| {
20387            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20388        })
20389        .await
20390        .unwrap();
20391    let editor = workspace
20392        .update(cx, |workspace, window, cx| {
20393            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20394        })
20395        .unwrap()
20396        .await
20397        .unwrap()
20398        .downcast::<Editor>()
20399        .unwrap();
20400
20401    let fake_server = fake_servers.next().await.unwrap();
20402    editor.update_in(cx, |editor, window, cx| {
20403        editor.set_text("<ad></ad>", window, cx);
20404        editor.change_selections(None, window, cx, |selections| {
20405            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20406        });
20407        let Some((buffer, _)) = editor
20408            .buffer
20409            .read(cx)
20410            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20411        else {
20412            panic!("Failed to get buffer for selection position");
20413        };
20414        let buffer = buffer.read(cx);
20415        let buffer_id = buffer.remote_id();
20416        let opening_range =
20417            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20418        let closing_range =
20419            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20420        let mut linked_ranges = HashMap::default();
20421        linked_ranges.insert(
20422            buffer_id,
20423            vec![(opening_range.clone(), vec![closing_range.clone()])],
20424        );
20425        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20426    });
20427    let mut completion_handle =
20428        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20429            Ok(Some(lsp::CompletionResponse::Array(vec![
20430                lsp::CompletionItem {
20431                    label: "head".to_string(),
20432                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20433                        lsp::InsertReplaceEdit {
20434                            new_text: "head".to_string(),
20435                            insert: lsp::Range::new(
20436                                lsp::Position::new(0, 1),
20437                                lsp::Position::new(0, 3),
20438                            ),
20439                            replace: lsp::Range::new(
20440                                lsp::Position::new(0, 1),
20441                                lsp::Position::new(0, 3),
20442                            ),
20443                        },
20444                    )),
20445                    ..Default::default()
20446                },
20447            ])))
20448        });
20449    editor.update_in(cx, |editor, window, cx| {
20450        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20451    });
20452    cx.run_until_parked();
20453    completion_handle.next().await.unwrap();
20454    editor.update(cx, |editor, _| {
20455        assert!(
20456            editor.context_menu_visible(),
20457            "Completion menu should be visible"
20458        );
20459    });
20460    editor.update_in(cx, |editor, window, cx| {
20461        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20462    });
20463    cx.executor().run_until_parked();
20464    editor.update(cx, |editor, cx| {
20465        assert_eq!(editor.text(cx), "<head></head>");
20466    });
20467}
20468
20469#[gpui::test]
20470async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20471    init_test(cx, |_| {});
20472
20473    let fs = FakeFs::new(cx.executor());
20474    fs.insert_tree(
20475        path!("/root"),
20476        json!({
20477            "a": {
20478                "main.rs": "fn main() {}",
20479            },
20480            "foo": {
20481                "bar": {
20482                    "external_file.rs": "pub mod external {}",
20483                }
20484            }
20485        }),
20486    )
20487    .await;
20488
20489    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20490    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20491    language_registry.add(rust_lang());
20492    let _fake_servers = language_registry.register_fake_lsp(
20493        "Rust",
20494        FakeLspAdapter {
20495            ..FakeLspAdapter::default()
20496        },
20497    );
20498    let (workspace, cx) =
20499        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20500    let worktree_id = workspace.update(cx, |workspace, cx| {
20501        workspace.project().update(cx, |project, cx| {
20502            project.worktrees(cx).next().unwrap().read(cx).id()
20503        })
20504    });
20505
20506    let assert_language_servers_count =
20507        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20508            project.update(cx, |project, cx| {
20509                let current = project
20510                    .lsp_store()
20511                    .read(cx)
20512                    .as_local()
20513                    .unwrap()
20514                    .language_servers
20515                    .len();
20516                assert_eq!(expected, current, "{context}");
20517            });
20518        };
20519
20520    assert_language_servers_count(
20521        0,
20522        "No servers should be running before any file is open",
20523        cx,
20524    );
20525    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20526    let main_editor = workspace
20527        .update_in(cx, |workspace, window, cx| {
20528            workspace.open_path(
20529                (worktree_id, "main.rs"),
20530                Some(pane.downgrade()),
20531                true,
20532                window,
20533                cx,
20534            )
20535        })
20536        .unwrap()
20537        .await
20538        .downcast::<Editor>()
20539        .unwrap();
20540    pane.update(cx, |pane, cx| {
20541        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20542        open_editor.update(cx, |editor, cx| {
20543            assert_eq!(
20544                editor.display_text(cx),
20545                "fn main() {}",
20546                "Original main.rs text on initial open",
20547            );
20548        });
20549        assert_eq!(open_editor, main_editor);
20550    });
20551    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20552
20553    let external_editor = workspace
20554        .update_in(cx, |workspace, window, cx| {
20555            workspace.open_abs_path(
20556                PathBuf::from("/root/foo/bar/external_file.rs"),
20557                OpenOptions::default(),
20558                window,
20559                cx,
20560            )
20561        })
20562        .await
20563        .expect("opening external file")
20564        .downcast::<Editor>()
20565        .expect("downcasted external file's open element to editor");
20566    pane.update(cx, |pane, cx| {
20567        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20568        open_editor.update(cx, |editor, cx| {
20569            assert_eq!(
20570                editor.display_text(cx),
20571                "pub mod external {}",
20572                "External file is open now",
20573            );
20574        });
20575        assert_eq!(open_editor, external_editor);
20576    });
20577    assert_language_servers_count(
20578        1,
20579        "Second, external, *.rs file should join the existing server",
20580        cx,
20581    );
20582
20583    pane.update_in(cx, |pane, window, cx| {
20584        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20585    })
20586    .unwrap()
20587    .await
20588    .unwrap();
20589    pane.update_in(cx, |pane, window, cx| {
20590        pane.navigate_backward(window, cx);
20591    });
20592    cx.run_until_parked();
20593    pane.update(cx, |pane, cx| {
20594        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20595        open_editor.update(cx, |editor, cx| {
20596            assert_eq!(
20597                editor.display_text(cx),
20598                "pub mod external {}",
20599                "External file is open now",
20600            );
20601        });
20602    });
20603    assert_language_servers_count(
20604        1,
20605        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20606        cx,
20607    );
20608
20609    cx.update(|_, cx| {
20610        workspace::reload(&workspace::Reload::default(), cx);
20611    });
20612    assert_language_servers_count(
20613        1,
20614        "After reloading the worktree with local and external files opened, only one project should be started",
20615        cx,
20616    );
20617}
20618
20619#[gpui::test]
20620async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20621    init_test(cx, |_| {});
20622
20623    let mut cx = EditorTestContext::new(cx).await;
20624    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20625    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20626
20627    // test cursor move to start of each line on tab
20628    // for `if`, `elif`, `else`, `while`, `with` and `for`
20629    cx.set_state(indoc! {"
20630        def main():
20631        ˇ    for item in items:
20632        ˇ        while item.active:
20633        ˇ            if item.value > 10:
20634        ˇ                continue
20635        ˇ            elif item.value < 0:
20636        ˇ                break
20637        ˇ            else:
20638        ˇ                with item.context() as ctx:
20639        ˇ                    yield count
20640        ˇ        else:
20641        ˇ            log('while else')
20642        ˇ    else:
20643        ˇ        log('for else')
20644    "});
20645    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20646    cx.assert_editor_state(indoc! {"
20647        def main():
20648            ˇfor item in items:
20649                ˇwhile item.active:
20650                    ˇif item.value > 10:
20651                        ˇcontinue
20652                    ˇelif item.value < 0:
20653                        ˇbreak
20654                    ˇelse:
20655                        ˇwith item.context() as ctx:
20656                            ˇyield count
20657                ˇelse:
20658                    ˇlog('while else')
20659            ˇelse:
20660                ˇlog('for else')
20661    "});
20662    // test relative indent is preserved when tab
20663    // for `if`, `elif`, `else`, `while`, `with` and `for`
20664    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20665    cx.assert_editor_state(indoc! {"
20666        def main():
20667                ˇfor item in items:
20668                    ˇwhile item.active:
20669                        ˇif item.value > 10:
20670                            ˇcontinue
20671                        ˇelif item.value < 0:
20672                            ˇbreak
20673                        ˇelse:
20674                            ˇwith item.context() as ctx:
20675                                ˇyield count
20676                    ˇelse:
20677                        ˇlog('while else')
20678                ˇelse:
20679                    ˇlog('for else')
20680    "});
20681
20682    // test cursor move to start of each line on tab
20683    // for `try`, `except`, `else`, `finally`, `match` and `def`
20684    cx.set_state(indoc! {"
20685        def main():
20686        ˇ    try:
20687        ˇ       fetch()
20688        ˇ    except ValueError:
20689        ˇ       handle_error()
20690        ˇ    else:
20691        ˇ        match value:
20692        ˇ            case _:
20693        ˇ    finally:
20694        ˇ        def status():
20695        ˇ            return 0
20696    "});
20697    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20698    cx.assert_editor_state(indoc! {"
20699        def main():
20700            ˇtry:
20701                ˇfetch()
20702            ˇexcept ValueError:
20703                ˇhandle_error()
20704            ˇelse:
20705                ˇmatch value:
20706                    ˇcase _:
20707            ˇfinally:
20708                ˇdef status():
20709                    ˇreturn 0
20710    "});
20711    // test relative indent is preserved when tab
20712    // for `try`, `except`, `else`, `finally`, `match` and `def`
20713    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20714    cx.assert_editor_state(indoc! {"
20715        def main():
20716                ˇtry:
20717                    ˇfetch()
20718                ˇexcept ValueError:
20719                    ˇhandle_error()
20720                ˇelse:
20721                    ˇmatch value:
20722                        ˇcase _:
20723                ˇfinally:
20724                    ˇdef status():
20725                        ˇreturn 0
20726    "});
20727}
20728
20729#[gpui::test]
20730async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
20731    init_test(cx, |_| {});
20732
20733    let mut cx = EditorTestContext::new(cx).await;
20734    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20735    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20736
20737    // test `else` auto outdents when typed inside `if` block
20738    cx.set_state(indoc! {"
20739        def main():
20740            if i == 2:
20741                return
20742                ˇ
20743    "});
20744    cx.update_editor(|editor, window, cx| {
20745        editor.handle_input("else:", window, cx);
20746    });
20747    cx.assert_editor_state(indoc! {"
20748        def main():
20749            if i == 2:
20750                return
20751            else:ˇ
20752    "});
20753
20754    // test `except` auto outdents when typed inside `try` block
20755    cx.set_state(indoc! {"
20756        def main():
20757            try:
20758                i = 2
20759                ˇ
20760    "});
20761    cx.update_editor(|editor, window, cx| {
20762        editor.handle_input("except:", window, cx);
20763    });
20764    cx.assert_editor_state(indoc! {"
20765        def main():
20766            try:
20767                i = 2
20768            except:ˇ
20769    "});
20770
20771    // test `else` auto outdents when typed inside `except` block
20772    cx.set_state(indoc! {"
20773        def main():
20774            try:
20775                i = 2
20776            except:
20777                j = 2
20778                ˇ
20779    "});
20780    cx.update_editor(|editor, window, cx| {
20781        editor.handle_input("else:", window, cx);
20782    });
20783    cx.assert_editor_state(indoc! {"
20784        def main():
20785            try:
20786                i = 2
20787            except:
20788                j = 2
20789            else:ˇ
20790    "});
20791
20792    // test `finally` auto outdents when typed inside `else` block
20793    cx.set_state(indoc! {"
20794        def main():
20795            try:
20796                i = 2
20797            except:
20798                j = 2
20799            else:
20800                k = 2
20801                ˇ
20802    "});
20803    cx.update_editor(|editor, window, cx| {
20804        editor.handle_input("finally:", window, cx);
20805    });
20806    cx.assert_editor_state(indoc! {"
20807        def main():
20808            try:
20809                i = 2
20810            except:
20811                j = 2
20812            else:
20813                k = 2
20814            finally:ˇ
20815    "});
20816
20817    // TODO: test `except` auto outdents when typed inside `try` block right after for block
20818    // cx.set_state(indoc! {"
20819    //     def main():
20820    //         try:
20821    //             for i in range(n):
20822    //                 pass
20823    //             ˇ
20824    // "});
20825    // cx.update_editor(|editor, window, cx| {
20826    //     editor.handle_input("except:", window, cx);
20827    // });
20828    // cx.assert_editor_state(indoc! {"
20829    //     def main():
20830    //         try:
20831    //             for i in range(n):
20832    //                 pass
20833    //         except:ˇ
20834    // "});
20835
20836    // TODO: test `else` auto outdents when typed inside `except` block right after for block
20837    // cx.set_state(indoc! {"
20838    //     def main():
20839    //         try:
20840    //             i = 2
20841    //         except:
20842    //             for i in range(n):
20843    //                 pass
20844    //             ˇ
20845    // "});
20846    // cx.update_editor(|editor, window, cx| {
20847    //     editor.handle_input("else:", window, cx);
20848    // });
20849    // cx.assert_editor_state(indoc! {"
20850    //     def main():
20851    //         try:
20852    //             i = 2
20853    //         except:
20854    //             for i in range(n):
20855    //                 pass
20856    //         else:ˇ
20857    // "});
20858
20859    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
20860    // cx.set_state(indoc! {"
20861    //     def main():
20862    //         try:
20863    //             i = 2
20864    //         except:
20865    //             j = 2
20866    //         else:
20867    //             for i in range(n):
20868    //                 pass
20869    //             ˇ
20870    // "});
20871    // cx.update_editor(|editor, window, cx| {
20872    //     editor.handle_input("finally:", window, cx);
20873    // });
20874    // cx.assert_editor_state(indoc! {"
20875    //     def main():
20876    //         try:
20877    //             i = 2
20878    //         except:
20879    //             j = 2
20880    //         else:
20881    //             for i in range(n):
20882    //                 pass
20883    //         finally:ˇ
20884    // "});
20885
20886    // test `else` stays at correct indent when typed after `for` block
20887    cx.set_state(indoc! {"
20888        def main():
20889            for i in range(10):
20890                if i == 3:
20891                    break
20892            ˇ
20893    "});
20894    cx.update_editor(|editor, window, cx| {
20895        editor.handle_input("else:", window, cx);
20896    });
20897    cx.assert_editor_state(indoc! {"
20898        def main():
20899            for i in range(10):
20900                if i == 3:
20901                    break
20902            else:ˇ
20903    "});
20904
20905    // test does not outdent on typing after line with square brackets
20906    cx.set_state(indoc! {"
20907        def f() -> list[str]:
20908            ˇ
20909    "});
20910    cx.update_editor(|editor, window, cx| {
20911        editor.handle_input("a", window, cx);
20912    });
20913    cx.assert_editor_state(indoc! {"
20914        def f() -> list[str]:
2091520916    "});
20917}
20918
20919#[gpui::test]
20920async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
20921    init_test(cx, |_| {});
20922    update_test_language_settings(cx, |settings| {
20923        settings.defaults.extend_comment_on_newline = Some(false);
20924    });
20925    let mut cx = EditorTestContext::new(cx).await;
20926    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20927    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20928
20929    // test correct indent after newline on comment
20930    cx.set_state(indoc! {"
20931        # COMMENT:ˇ
20932    "});
20933    cx.update_editor(|editor, window, cx| {
20934        editor.newline(&Newline, window, cx);
20935    });
20936    cx.assert_editor_state(indoc! {"
20937        # COMMENT:
20938        ˇ
20939    "});
20940
20941    // test correct indent after newline in brackets
20942    cx.set_state(indoc! {"
20943        {ˇ}
20944    "});
20945    cx.update_editor(|editor, window, cx| {
20946        editor.newline(&Newline, window, cx);
20947    });
20948    cx.run_until_parked();
20949    cx.assert_editor_state(indoc! {"
20950        {
20951            ˇ
20952        }
20953    "});
20954
20955    cx.set_state(indoc! {"
20956        (ˇ)
20957    "});
20958    cx.update_editor(|editor, window, cx| {
20959        editor.newline(&Newline, window, cx);
20960    });
20961    cx.run_until_parked();
20962    cx.assert_editor_state(indoc! {"
20963        (
20964            ˇ
20965        )
20966    "});
20967
20968    // do not indent after empty lists or dictionaries
20969    cx.set_state(indoc! {"
20970        a = []ˇ
20971    "});
20972    cx.update_editor(|editor, window, cx| {
20973        editor.newline(&Newline, window, cx);
20974    });
20975    cx.run_until_parked();
20976    cx.assert_editor_state(indoc! {"
20977        a = []
20978        ˇ
20979    "});
20980}
20981
20982fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
20983    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
20984    point..point
20985}
20986
20987fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
20988    let (text, ranges) = marked_text_ranges(marked_text, true);
20989    assert_eq!(editor.text(cx), text);
20990    assert_eq!(
20991        editor.selections.ranges(cx),
20992        ranges,
20993        "Assert selections are {}",
20994        marked_text
20995    );
20996}
20997
20998pub fn handle_signature_help_request(
20999    cx: &mut EditorLspTestContext,
21000    mocked_response: lsp::SignatureHelp,
21001) -> impl Future<Output = ()> + use<> {
21002    let mut request =
21003        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21004            let mocked_response = mocked_response.clone();
21005            async move { Ok(Some(mocked_response)) }
21006        });
21007
21008    async move {
21009        request.next().await;
21010    }
21011}
21012
21013/// Handle completion request passing a marked string specifying where the completion
21014/// should be triggered from using '|' character, what range should be replaced, and what completions
21015/// should be returned using '<' and '>' to delimit the range.
21016///
21017/// Also see `handle_completion_request_with_insert_and_replace`.
21018#[track_caller]
21019pub fn handle_completion_request(
21020    cx: &mut EditorLspTestContext,
21021    marked_string: &str,
21022    completions: Vec<&'static str>,
21023    counter: Arc<AtomicUsize>,
21024) -> impl Future<Output = ()> {
21025    let complete_from_marker: TextRangeMarker = '|'.into();
21026    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21027    let (_, mut marked_ranges) = marked_text_ranges_by(
21028        marked_string,
21029        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21030    );
21031
21032    let complete_from_position =
21033        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21034    let replace_range =
21035        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21036
21037    let mut request =
21038        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21039            let completions = completions.clone();
21040            counter.fetch_add(1, atomic::Ordering::Release);
21041            async move {
21042                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21043                assert_eq!(
21044                    params.text_document_position.position,
21045                    complete_from_position
21046                );
21047                Ok(Some(lsp::CompletionResponse::Array(
21048                    completions
21049                        .iter()
21050                        .map(|completion_text| lsp::CompletionItem {
21051                            label: completion_text.to_string(),
21052                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21053                                range: replace_range,
21054                                new_text: completion_text.to_string(),
21055                            })),
21056                            ..Default::default()
21057                        })
21058                        .collect(),
21059                )))
21060            }
21061        });
21062
21063    async move {
21064        request.next().await;
21065    }
21066}
21067
21068/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21069/// given instead, which also contains an `insert` range.
21070///
21071/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
21072/// that is, `replace_range.start..cursor_pos`.
21073pub fn handle_completion_request_with_insert_and_replace(
21074    cx: &mut EditorLspTestContext,
21075    marked_string: &str,
21076    completions: Vec<&'static str>,
21077    counter: Arc<AtomicUsize>,
21078) -> impl Future<Output = ()> {
21079    let complete_from_marker: TextRangeMarker = '|'.into();
21080    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21081    let (_, mut marked_ranges) = marked_text_ranges_by(
21082        marked_string,
21083        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21084    );
21085
21086    let complete_from_position =
21087        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21088    let replace_range =
21089        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21090
21091    let mut request =
21092        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21093            let completions = completions.clone();
21094            counter.fetch_add(1, atomic::Ordering::Release);
21095            async move {
21096                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21097                assert_eq!(
21098                    params.text_document_position.position, complete_from_position,
21099                    "marker `|` position doesn't match",
21100                );
21101                Ok(Some(lsp::CompletionResponse::Array(
21102                    completions
21103                        .iter()
21104                        .map(|completion_text| lsp::CompletionItem {
21105                            label: completion_text.to_string(),
21106                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21107                                lsp::InsertReplaceEdit {
21108                                    insert: lsp::Range {
21109                                        start: replace_range.start,
21110                                        end: complete_from_position,
21111                                    },
21112                                    replace: replace_range,
21113                                    new_text: completion_text.to_string(),
21114                                },
21115                            )),
21116                            ..Default::default()
21117                        })
21118                        .collect(),
21119                )))
21120            }
21121        });
21122
21123    async move {
21124        request.next().await;
21125    }
21126}
21127
21128fn handle_resolve_completion_request(
21129    cx: &mut EditorLspTestContext,
21130    edits: Option<Vec<(&'static str, &'static str)>>,
21131) -> impl Future<Output = ()> {
21132    let edits = edits.map(|edits| {
21133        edits
21134            .iter()
21135            .map(|(marked_string, new_text)| {
21136                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21137                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21138                lsp::TextEdit::new(replace_range, new_text.to_string())
21139            })
21140            .collect::<Vec<_>>()
21141    });
21142
21143    let mut request =
21144        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21145            let edits = edits.clone();
21146            async move {
21147                Ok(lsp::CompletionItem {
21148                    additional_text_edits: edits,
21149                    ..Default::default()
21150                })
21151            }
21152        });
21153
21154    async move {
21155        request.next().await;
21156    }
21157}
21158
21159pub(crate) fn update_test_language_settings(
21160    cx: &mut TestAppContext,
21161    f: impl Fn(&mut AllLanguageSettingsContent),
21162) {
21163    cx.update(|cx| {
21164        SettingsStore::update_global(cx, |store, cx| {
21165            store.update_user_settings::<AllLanguageSettings>(cx, f);
21166        });
21167    });
21168}
21169
21170pub(crate) fn update_test_project_settings(
21171    cx: &mut TestAppContext,
21172    f: impl Fn(&mut ProjectSettings),
21173) {
21174    cx.update(|cx| {
21175        SettingsStore::update_global(cx, |store, cx| {
21176            store.update_user_settings::<ProjectSettings>(cx, f);
21177        });
21178    });
21179}
21180
21181pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21182    cx.update(|cx| {
21183        assets::Assets.load_test_fonts(cx);
21184        let store = SettingsStore::test(cx);
21185        cx.set_global(store);
21186        theme::init(theme::LoadThemes::JustBase, cx);
21187        release_channel::init(SemanticVersion::default(), cx);
21188        client::init_settings(cx);
21189        language::init(cx);
21190        Project::init_settings(cx);
21191        workspace::init_settings(cx);
21192        crate::init(cx);
21193    });
21194
21195    update_test_language_settings(cx, f);
21196}
21197
21198#[track_caller]
21199fn assert_hunk_revert(
21200    not_reverted_text_with_selections: &str,
21201    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21202    expected_reverted_text_with_selections: &str,
21203    base_text: &str,
21204    cx: &mut EditorLspTestContext,
21205) {
21206    cx.set_state(not_reverted_text_with_selections);
21207    cx.set_head_text(base_text);
21208    cx.executor().run_until_parked();
21209
21210    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21211        let snapshot = editor.snapshot(window, cx);
21212        let reverted_hunk_statuses = snapshot
21213            .buffer_snapshot
21214            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21215            .map(|hunk| hunk.status().kind)
21216            .collect::<Vec<_>>();
21217
21218        editor.git_restore(&Default::default(), window, cx);
21219        reverted_hunk_statuses
21220    });
21221    cx.executor().run_until_parked();
21222    cx.assert_editor_state(expected_reverted_text_with_selections);
21223    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21224}