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![]))
 9115        })
 9116        .next()
 9117        .await;
 9118    cx.executor().start_waiting();
 9119    save.await;
 9120}
 9121
 9122#[gpui::test]
 9123async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9124    init_test(cx, |settings| {
 9125        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9126            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9127        ))
 9128    });
 9129
 9130    let fs = FakeFs::new(cx.executor());
 9131    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9132
 9133    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9134
 9135    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9136    language_registry.add(Arc::new(Language::new(
 9137        LanguageConfig {
 9138            name: "Rust".into(),
 9139            matcher: LanguageMatcher {
 9140                path_suffixes: vec!["rs".to_string()],
 9141                ..Default::default()
 9142            },
 9143            ..LanguageConfig::default()
 9144        },
 9145        Some(tree_sitter_rust::LANGUAGE.into()),
 9146    )));
 9147    update_test_language_settings(cx, |settings| {
 9148        // Enable Prettier formatting for the same buffer, and ensure
 9149        // LSP is called instead of Prettier.
 9150        settings.defaults.prettier = Some(PrettierSettings {
 9151            allowed: true,
 9152            ..PrettierSettings::default()
 9153        });
 9154    });
 9155    let mut fake_servers = language_registry.register_fake_lsp(
 9156        "Rust",
 9157        FakeLspAdapter {
 9158            capabilities: lsp::ServerCapabilities {
 9159                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9160                ..Default::default()
 9161            },
 9162            ..Default::default()
 9163        },
 9164    );
 9165
 9166    let buffer = project
 9167        .update(cx, |project, cx| {
 9168            project.open_local_buffer(path!("/file.rs"), cx)
 9169        })
 9170        .await
 9171        .unwrap();
 9172
 9173    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9174    let (editor, cx) = cx.add_window_view(|window, cx| {
 9175        build_editor_with_project(project.clone(), buffer, window, cx)
 9176    });
 9177    editor.update_in(cx, |editor, window, cx| {
 9178        editor.set_text("one\ntwo\nthree\n", window, cx)
 9179    });
 9180
 9181    cx.executor().start_waiting();
 9182    let fake_server = fake_servers.next().await.unwrap();
 9183
 9184    let format = editor
 9185        .update_in(cx, |editor, window, cx| {
 9186            editor.perform_format(
 9187                project.clone(),
 9188                FormatTrigger::Manual,
 9189                FormatTarget::Buffers,
 9190                window,
 9191                cx,
 9192            )
 9193        })
 9194        .unwrap();
 9195    fake_server
 9196        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9197            assert_eq!(
 9198                params.text_document.uri,
 9199                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9200            );
 9201            assert_eq!(params.options.tab_size, 4);
 9202            Ok(Some(vec![lsp::TextEdit::new(
 9203                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9204                ", ".to_string(),
 9205            )]))
 9206        })
 9207        .next()
 9208        .await;
 9209    cx.executor().start_waiting();
 9210    format.await;
 9211    assert_eq!(
 9212        editor.update(cx, |editor, cx| editor.text(cx)),
 9213        "one, two\nthree\n"
 9214    );
 9215
 9216    editor.update_in(cx, |editor, window, cx| {
 9217        editor.set_text("one\ntwo\nthree\n", window, cx)
 9218    });
 9219    // Ensure we don't lock if formatting hangs.
 9220    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9221        move |params, _| async move {
 9222            assert_eq!(
 9223                params.text_document.uri,
 9224                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9225            );
 9226            futures::future::pending::<()>().await;
 9227            unreachable!()
 9228        },
 9229    );
 9230    let format = editor
 9231        .update_in(cx, |editor, window, cx| {
 9232            editor.perform_format(
 9233                project,
 9234                FormatTrigger::Manual,
 9235                FormatTarget::Buffers,
 9236                window,
 9237                cx,
 9238            )
 9239        })
 9240        .unwrap();
 9241    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9242    cx.executor().start_waiting();
 9243    format.await;
 9244    assert_eq!(
 9245        editor.update(cx, |editor, cx| editor.text(cx)),
 9246        "one\ntwo\nthree\n"
 9247    );
 9248}
 9249
 9250#[gpui::test]
 9251async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9252    init_test(cx, |settings| {
 9253        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9254        settings.defaults.formatter =
 9255            Some(language_settings::SelectedFormatter::List(FormatterList(
 9256                vec![
 9257                    Formatter::LanguageServer { name: None },
 9258                    Formatter::CodeActions(
 9259                        [
 9260                            ("code-action-1".into(), true),
 9261                            ("code-action-2".into(), true),
 9262                        ]
 9263                        .into_iter()
 9264                        .collect(),
 9265                    ),
 9266                ]
 9267                .into(),
 9268            )))
 9269    });
 9270
 9271    let fs = FakeFs::new(cx.executor());
 9272    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9273        .await;
 9274
 9275    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9276    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9277    language_registry.add(rust_lang());
 9278
 9279    let mut fake_servers = language_registry.register_fake_lsp(
 9280        "Rust",
 9281        FakeLspAdapter {
 9282            capabilities: lsp::ServerCapabilities {
 9283                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9284                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9285                    commands: vec!["the-command-for-code-action-1".into()],
 9286                    ..Default::default()
 9287                }),
 9288                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9289                ..Default::default()
 9290            },
 9291            ..Default::default()
 9292        },
 9293    );
 9294
 9295    let buffer = project
 9296        .update(cx, |project, cx| {
 9297            project.open_local_buffer(path!("/file.rs"), cx)
 9298        })
 9299        .await
 9300        .unwrap();
 9301
 9302    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9303    let (editor, cx) = cx.add_window_view(|window, cx| {
 9304        build_editor_with_project(project.clone(), buffer, window, cx)
 9305    });
 9306
 9307    cx.executor().start_waiting();
 9308
 9309    let fake_server = fake_servers.next().await.unwrap();
 9310    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9311        move |_params, _| async move {
 9312            Ok(Some(vec![lsp::TextEdit::new(
 9313                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9314                "applied-formatting\n".to_string(),
 9315            )]))
 9316        },
 9317    );
 9318    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9319        move |params, _| async move {
 9320            assert_eq!(
 9321                params.context.only,
 9322                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9323            );
 9324            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9325            Ok(Some(vec![
 9326                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9327                    kind: Some("code-action-1".into()),
 9328                    edit: Some(lsp::WorkspaceEdit::new(
 9329                        [(
 9330                            uri.clone(),
 9331                            vec![lsp::TextEdit::new(
 9332                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9333                                "applied-code-action-1-edit\n".to_string(),
 9334                            )],
 9335                        )]
 9336                        .into_iter()
 9337                        .collect(),
 9338                    )),
 9339                    command: Some(lsp::Command {
 9340                        command: "the-command-for-code-action-1".into(),
 9341                        ..Default::default()
 9342                    }),
 9343                    ..Default::default()
 9344                }),
 9345                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9346                    kind: Some("code-action-2".into()),
 9347                    edit: Some(lsp::WorkspaceEdit::new(
 9348                        [(
 9349                            uri.clone(),
 9350                            vec![lsp::TextEdit::new(
 9351                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9352                                "applied-code-action-2-edit\n".to_string(),
 9353                            )],
 9354                        )]
 9355                        .into_iter()
 9356                        .collect(),
 9357                    )),
 9358                    ..Default::default()
 9359                }),
 9360            ]))
 9361        },
 9362    );
 9363
 9364    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9365        move |params, _| async move { Ok(params) }
 9366    });
 9367
 9368    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9369    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9370        let fake = fake_server.clone();
 9371        let lock = command_lock.clone();
 9372        move |params, _| {
 9373            assert_eq!(params.command, "the-command-for-code-action-1");
 9374            let fake = fake.clone();
 9375            let lock = lock.clone();
 9376            async move {
 9377                lock.lock().await;
 9378                fake.server
 9379                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9380                        label: None,
 9381                        edit: lsp::WorkspaceEdit {
 9382                            changes: Some(
 9383                                [(
 9384                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9385                                    vec![lsp::TextEdit {
 9386                                        range: lsp::Range::new(
 9387                                            lsp::Position::new(0, 0),
 9388                                            lsp::Position::new(0, 0),
 9389                                        ),
 9390                                        new_text: "applied-code-action-1-command\n".into(),
 9391                                    }],
 9392                                )]
 9393                                .into_iter()
 9394                                .collect(),
 9395                            ),
 9396                            ..Default::default()
 9397                        },
 9398                    })
 9399                    .await
 9400                    .into_response()
 9401                    .unwrap();
 9402                Ok(Some(json!(null)))
 9403            }
 9404        }
 9405    });
 9406
 9407    cx.executor().start_waiting();
 9408    editor
 9409        .update_in(cx, |editor, window, cx| {
 9410            editor.perform_format(
 9411                project.clone(),
 9412                FormatTrigger::Manual,
 9413                FormatTarget::Buffers,
 9414                window,
 9415                cx,
 9416            )
 9417        })
 9418        .unwrap()
 9419        .await;
 9420    editor.update(cx, |editor, cx| {
 9421        assert_eq!(
 9422            editor.text(cx),
 9423            r#"
 9424                applied-code-action-2-edit
 9425                applied-code-action-1-command
 9426                applied-code-action-1-edit
 9427                applied-formatting
 9428                one
 9429                two
 9430                three
 9431            "#
 9432            .unindent()
 9433        );
 9434    });
 9435
 9436    editor.update_in(cx, |editor, window, cx| {
 9437        editor.undo(&Default::default(), window, cx);
 9438        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9439    });
 9440
 9441    // Perform a manual edit while waiting for an LSP command
 9442    // that's being run as part of a formatting code action.
 9443    let lock_guard = command_lock.lock().await;
 9444    let format = editor
 9445        .update_in(cx, |editor, window, cx| {
 9446            editor.perform_format(
 9447                project.clone(),
 9448                FormatTrigger::Manual,
 9449                FormatTarget::Buffers,
 9450                window,
 9451                cx,
 9452            )
 9453        })
 9454        .unwrap();
 9455    cx.run_until_parked();
 9456    editor.update(cx, |editor, cx| {
 9457        assert_eq!(
 9458            editor.text(cx),
 9459            r#"
 9460                applied-code-action-1-edit
 9461                applied-formatting
 9462                one
 9463                two
 9464                three
 9465            "#
 9466            .unindent()
 9467        );
 9468
 9469        editor.buffer.update(cx, |buffer, cx| {
 9470            let ix = buffer.len(cx);
 9471            buffer.edit([(ix..ix, "edited\n")], None, cx);
 9472        });
 9473    });
 9474
 9475    // Allow the LSP command to proceed. Because the buffer was edited,
 9476    // the second code action will not be run.
 9477    drop(lock_guard);
 9478    format.await;
 9479    editor.update_in(cx, |editor, window, cx| {
 9480        assert_eq!(
 9481            editor.text(cx),
 9482            r#"
 9483                applied-code-action-1-command
 9484                applied-code-action-1-edit
 9485                applied-formatting
 9486                one
 9487                two
 9488                three
 9489                edited
 9490            "#
 9491            .unindent()
 9492        );
 9493
 9494        // The manual edit is undone first, because it is the last thing the user did
 9495        // (even though the command completed afterwards).
 9496        editor.undo(&Default::default(), window, cx);
 9497        assert_eq!(
 9498            editor.text(cx),
 9499            r#"
 9500                applied-code-action-1-command
 9501                applied-code-action-1-edit
 9502                applied-formatting
 9503                one
 9504                two
 9505                three
 9506            "#
 9507            .unindent()
 9508        );
 9509
 9510        // All the formatting (including the command, which completed after the manual edit)
 9511        // is undone together.
 9512        editor.undo(&Default::default(), window, cx);
 9513        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
 9514    });
 9515}
 9516
 9517#[gpui::test]
 9518async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 9519    init_test(cx, |settings| {
 9520        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9521            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9522        ))
 9523    });
 9524
 9525    let fs = FakeFs::new(cx.executor());
 9526    fs.insert_file(path!("/file.ts"), Default::default()).await;
 9527
 9528    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9529
 9530    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9531    language_registry.add(Arc::new(Language::new(
 9532        LanguageConfig {
 9533            name: "TypeScript".into(),
 9534            matcher: LanguageMatcher {
 9535                path_suffixes: vec!["ts".to_string()],
 9536                ..Default::default()
 9537            },
 9538            ..LanguageConfig::default()
 9539        },
 9540        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9541    )));
 9542    update_test_language_settings(cx, |settings| {
 9543        settings.defaults.prettier = Some(PrettierSettings {
 9544            allowed: true,
 9545            ..PrettierSettings::default()
 9546        });
 9547    });
 9548    let mut fake_servers = language_registry.register_fake_lsp(
 9549        "TypeScript",
 9550        FakeLspAdapter {
 9551            capabilities: lsp::ServerCapabilities {
 9552                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9553                ..Default::default()
 9554            },
 9555            ..Default::default()
 9556        },
 9557    );
 9558
 9559    let buffer = project
 9560        .update(cx, |project, cx| {
 9561            project.open_local_buffer(path!("/file.ts"), cx)
 9562        })
 9563        .await
 9564        .unwrap();
 9565
 9566    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9567    let (editor, cx) = cx.add_window_view(|window, cx| {
 9568        build_editor_with_project(project.clone(), buffer, window, cx)
 9569    });
 9570    editor.update_in(cx, |editor, window, cx| {
 9571        editor.set_text(
 9572            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9573            window,
 9574            cx,
 9575        )
 9576    });
 9577
 9578    cx.executor().start_waiting();
 9579    let fake_server = fake_servers.next().await.unwrap();
 9580
 9581    let format = editor
 9582        .update_in(cx, |editor, window, cx| {
 9583            editor.perform_code_action_kind(
 9584                project.clone(),
 9585                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9586                window,
 9587                cx,
 9588            )
 9589        })
 9590        .unwrap();
 9591    fake_server
 9592        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 9593            assert_eq!(
 9594                params.text_document.uri,
 9595                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9596            );
 9597            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 9598                lsp::CodeAction {
 9599                    title: "Organize Imports".to_string(),
 9600                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 9601                    edit: Some(lsp::WorkspaceEdit {
 9602                        changes: Some(
 9603                            [(
 9604                                params.text_document.uri.clone(),
 9605                                vec![lsp::TextEdit::new(
 9606                                    lsp::Range::new(
 9607                                        lsp::Position::new(1, 0),
 9608                                        lsp::Position::new(2, 0),
 9609                                    ),
 9610                                    "".to_string(),
 9611                                )],
 9612                            )]
 9613                            .into_iter()
 9614                            .collect(),
 9615                        ),
 9616                        ..Default::default()
 9617                    }),
 9618                    ..Default::default()
 9619                },
 9620            )]))
 9621        })
 9622        .next()
 9623        .await;
 9624    cx.executor().start_waiting();
 9625    format.await;
 9626    assert_eq!(
 9627        editor.update(cx, |editor, cx| editor.text(cx)),
 9628        "import { a } from 'module';\n\nconst x = a;\n"
 9629    );
 9630
 9631    editor.update_in(cx, |editor, window, cx| {
 9632        editor.set_text(
 9633            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 9634            window,
 9635            cx,
 9636        )
 9637    });
 9638    // Ensure we don't lock if code action hangs.
 9639    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9640        move |params, _| async move {
 9641            assert_eq!(
 9642                params.text_document.uri,
 9643                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 9644            );
 9645            futures::future::pending::<()>().await;
 9646            unreachable!()
 9647        },
 9648    );
 9649    let format = editor
 9650        .update_in(cx, |editor, window, cx| {
 9651            editor.perform_code_action_kind(
 9652                project,
 9653                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 9654                window,
 9655                cx,
 9656            )
 9657        })
 9658        .unwrap();
 9659    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 9660    cx.executor().start_waiting();
 9661    format.await;
 9662    assert_eq!(
 9663        editor.update(cx, |editor, cx| editor.text(cx)),
 9664        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 9665    );
 9666}
 9667
 9668#[gpui::test]
 9669async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 9670    init_test(cx, |_| {});
 9671
 9672    let mut cx = EditorLspTestContext::new_rust(
 9673        lsp::ServerCapabilities {
 9674            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9675            ..Default::default()
 9676        },
 9677        cx,
 9678    )
 9679    .await;
 9680
 9681    cx.set_state(indoc! {"
 9682        one.twoˇ
 9683    "});
 9684
 9685    // The format request takes a long time. When it completes, it inserts
 9686    // a newline and an indent before the `.`
 9687    cx.lsp
 9688        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
 9689            let executor = cx.background_executor().clone();
 9690            async move {
 9691                executor.timer(Duration::from_millis(100)).await;
 9692                Ok(Some(vec![lsp::TextEdit {
 9693                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 9694                    new_text: "\n    ".into(),
 9695                }]))
 9696            }
 9697        });
 9698
 9699    // Submit a format request.
 9700    let format_1 = cx
 9701        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9702        .unwrap();
 9703    cx.executor().run_until_parked();
 9704
 9705    // Submit a second format request.
 9706    let format_2 = cx
 9707        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9708        .unwrap();
 9709    cx.executor().run_until_parked();
 9710
 9711    // Wait for both format requests to complete
 9712    cx.executor().advance_clock(Duration::from_millis(200));
 9713    cx.executor().start_waiting();
 9714    format_1.await.unwrap();
 9715    cx.executor().start_waiting();
 9716    format_2.await.unwrap();
 9717
 9718    // The formatting edits only happens once.
 9719    cx.assert_editor_state(indoc! {"
 9720        one
 9721            .twoˇ
 9722    "});
 9723}
 9724
 9725#[gpui::test]
 9726async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 9727    init_test(cx, |settings| {
 9728        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 9729    });
 9730
 9731    let mut cx = EditorLspTestContext::new_rust(
 9732        lsp::ServerCapabilities {
 9733            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9734            ..Default::default()
 9735        },
 9736        cx,
 9737    )
 9738    .await;
 9739
 9740    // Set up a buffer white some trailing whitespace and no trailing newline.
 9741    cx.set_state(
 9742        &[
 9743            "one ",   //
 9744            "twoˇ",   //
 9745            "three ", //
 9746            "four",   //
 9747        ]
 9748        .join("\n"),
 9749    );
 9750
 9751    // Submit a format request.
 9752    let format = cx
 9753        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 9754        .unwrap();
 9755
 9756    // Record which buffer changes have been sent to the language server
 9757    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 9758    cx.lsp
 9759        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 9760            let buffer_changes = buffer_changes.clone();
 9761            move |params, _| {
 9762                buffer_changes.lock().extend(
 9763                    params
 9764                        .content_changes
 9765                        .into_iter()
 9766                        .map(|e| (e.range.unwrap(), e.text)),
 9767                );
 9768            }
 9769        });
 9770
 9771    // Handle formatting requests to the language server.
 9772    cx.lsp
 9773        .set_request_handler::<lsp::request::Formatting, _, _>({
 9774            let buffer_changes = buffer_changes.clone();
 9775            move |_, _| {
 9776                // When formatting is requested, trailing whitespace has already been stripped,
 9777                // and the trailing newline has already been added.
 9778                assert_eq!(
 9779                    &buffer_changes.lock()[1..],
 9780                    &[
 9781                        (
 9782                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 9783                            "".into()
 9784                        ),
 9785                        (
 9786                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 9787                            "".into()
 9788                        ),
 9789                        (
 9790                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 9791                            "\n".into()
 9792                        ),
 9793                    ]
 9794                );
 9795
 9796                // Insert blank lines between each line of the buffer.
 9797                async move {
 9798                    Ok(Some(vec![
 9799                        lsp::TextEdit {
 9800                            range: lsp::Range::new(
 9801                                lsp::Position::new(1, 0),
 9802                                lsp::Position::new(1, 0),
 9803                            ),
 9804                            new_text: "\n".into(),
 9805                        },
 9806                        lsp::TextEdit {
 9807                            range: lsp::Range::new(
 9808                                lsp::Position::new(2, 0),
 9809                                lsp::Position::new(2, 0),
 9810                            ),
 9811                            new_text: "\n".into(),
 9812                        },
 9813                    ]))
 9814                }
 9815            }
 9816        });
 9817
 9818    // After formatting the buffer, the trailing whitespace is stripped,
 9819    // a newline is appended, and the edits provided by the language server
 9820    // have been applied.
 9821    format.await.unwrap();
 9822    cx.assert_editor_state(
 9823        &[
 9824            "one",   //
 9825            "",      //
 9826            "twoˇ",  //
 9827            "",      //
 9828            "three", //
 9829            "four",  //
 9830            "",      //
 9831        ]
 9832        .join("\n"),
 9833    );
 9834
 9835    // Undoing the formatting undoes the trailing whitespace removal, the
 9836    // trailing newline, and the LSP edits.
 9837    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 9838    cx.assert_editor_state(
 9839        &[
 9840            "one ",   //
 9841            "twoˇ",   //
 9842            "three ", //
 9843            "four",   //
 9844        ]
 9845        .join("\n"),
 9846    );
 9847}
 9848
 9849#[gpui::test]
 9850async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 9851    cx: &mut TestAppContext,
 9852) {
 9853    init_test(cx, |_| {});
 9854
 9855    cx.update(|cx| {
 9856        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9857            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9858                settings.auto_signature_help = Some(true);
 9859            });
 9860        });
 9861    });
 9862
 9863    let mut cx = EditorLspTestContext::new_rust(
 9864        lsp::ServerCapabilities {
 9865            signature_help_provider: Some(lsp::SignatureHelpOptions {
 9866                ..Default::default()
 9867            }),
 9868            ..Default::default()
 9869        },
 9870        cx,
 9871    )
 9872    .await;
 9873
 9874    let language = Language::new(
 9875        LanguageConfig {
 9876            name: "Rust".into(),
 9877            brackets: BracketPairConfig {
 9878                pairs: vec![
 9879                    BracketPair {
 9880                        start: "{".to_string(),
 9881                        end: "}".to_string(),
 9882                        close: true,
 9883                        surround: true,
 9884                        newline: true,
 9885                    },
 9886                    BracketPair {
 9887                        start: "(".to_string(),
 9888                        end: ")".to_string(),
 9889                        close: true,
 9890                        surround: true,
 9891                        newline: true,
 9892                    },
 9893                    BracketPair {
 9894                        start: "/*".to_string(),
 9895                        end: " */".to_string(),
 9896                        close: true,
 9897                        surround: true,
 9898                        newline: true,
 9899                    },
 9900                    BracketPair {
 9901                        start: "[".to_string(),
 9902                        end: "]".to_string(),
 9903                        close: false,
 9904                        surround: false,
 9905                        newline: true,
 9906                    },
 9907                    BracketPair {
 9908                        start: "\"".to_string(),
 9909                        end: "\"".to_string(),
 9910                        close: true,
 9911                        surround: true,
 9912                        newline: false,
 9913                    },
 9914                    BracketPair {
 9915                        start: "<".to_string(),
 9916                        end: ">".to_string(),
 9917                        close: false,
 9918                        surround: true,
 9919                        newline: true,
 9920                    },
 9921                ],
 9922                ..Default::default()
 9923            },
 9924            autoclose_before: "})]".to_string(),
 9925            ..Default::default()
 9926        },
 9927        Some(tree_sitter_rust::LANGUAGE.into()),
 9928    );
 9929    let language = Arc::new(language);
 9930
 9931    cx.language_registry().add(language.clone());
 9932    cx.update_buffer(|buffer, cx| {
 9933        buffer.set_language(Some(language), cx);
 9934    });
 9935
 9936    cx.set_state(
 9937        &r#"
 9938            fn main() {
 9939                sampleˇ
 9940            }
 9941        "#
 9942        .unindent(),
 9943    );
 9944
 9945    cx.update_editor(|editor, window, cx| {
 9946        editor.handle_input("(", window, cx);
 9947    });
 9948    cx.assert_editor_state(
 9949        &"
 9950            fn main() {
 9951                sample(ˇ)
 9952            }
 9953        "
 9954        .unindent(),
 9955    );
 9956
 9957    let mocked_response = lsp::SignatureHelp {
 9958        signatures: vec![lsp::SignatureInformation {
 9959            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9960            documentation: None,
 9961            parameters: Some(vec![
 9962                lsp::ParameterInformation {
 9963                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9964                    documentation: None,
 9965                },
 9966                lsp::ParameterInformation {
 9967                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9968                    documentation: None,
 9969                },
 9970            ]),
 9971            active_parameter: None,
 9972        }],
 9973        active_signature: Some(0),
 9974        active_parameter: Some(0),
 9975    };
 9976    handle_signature_help_request(&mut cx, mocked_response).await;
 9977
 9978    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9979        .await;
 9980
 9981    cx.editor(|editor, _, _| {
 9982        let signature_help_state = editor.signature_help_state.popover().cloned();
 9983        assert_eq!(
 9984            signature_help_state.unwrap().label,
 9985            "param1: u8, param2: u8"
 9986        );
 9987    });
 9988}
 9989
 9990#[gpui::test]
 9991async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 9992    init_test(cx, |_| {});
 9993
 9994    cx.update(|cx| {
 9995        cx.update_global::<SettingsStore, _>(|settings, cx| {
 9996            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 9997                settings.auto_signature_help = Some(false);
 9998                settings.show_signature_help_after_edits = Some(false);
 9999            });
10000        });
10001    });
10002
10003    let mut cx = EditorLspTestContext::new_rust(
10004        lsp::ServerCapabilities {
10005            signature_help_provider: Some(lsp::SignatureHelpOptions {
10006                ..Default::default()
10007            }),
10008            ..Default::default()
10009        },
10010        cx,
10011    )
10012    .await;
10013
10014    let language = Language::new(
10015        LanguageConfig {
10016            name: "Rust".into(),
10017            brackets: BracketPairConfig {
10018                pairs: vec![
10019                    BracketPair {
10020                        start: "{".to_string(),
10021                        end: "}".to_string(),
10022                        close: true,
10023                        surround: true,
10024                        newline: true,
10025                    },
10026                    BracketPair {
10027                        start: "(".to_string(),
10028                        end: ")".to_string(),
10029                        close: true,
10030                        surround: true,
10031                        newline: true,
10032                    },
10033                    BracketPair {
10034                        start: "/*".to_string(),
10035                        end: " */".to_string(),
10036                        close: true,
10037                        surround: true,
10038                        newline: true,
10039                    },
10040                    BracketPair {
10041                        start: "[".to_string(),
10042                        end: "]".to_string(),
10043                        close: false,
10044                        surround: false,
10045                        newline: true,
10046                    },
10047                    BracketPair {
10048                        start: "\"".to_string(),
10049                        end: "\"".to_string(),
10050                        close: true,
10051                        surround: true,
10052                        newline: false,
10053                    },
10054                    BracketPair {
10055                        start: "<".to_string(),
10056                        end: ">".to_string(),
10057                        close: false,
10058                        surround: true,
10059                        newline: true,
10060                    },
10061                ],
10062                ..Default::default()
10063            },
10064            autoclose_before: "})]".to_string(),
10065            ..Default::default()
10066        },
10067        Some(tree_sitter_rust::LANGUAGE.into()),
10068    );
10069    let language = Arc::new(language);
10070
10071    cx.language_registry().add(language.clone());
10072    cx.update_buffer(|buffer, cx| {
10073        buffer.set_language(Some(language), cx);
10074    });
10075
10076    // Ensure that signature_help is not called when no signature help is enabled.
10077    cx.set_state(
10078        &r#"
10079            fn main() {
10080                sampleˇ
10081            }
10082        "#
10083        .unindent(),
10084    );
10085    cx.update_editor(|editor, window, cx| {
10086        editor.handle_input("(", window, cx);
10087    });
10088    cx.assert_editor_state(
10089        &"
10090            fn main() {
10091                sample(ˇ)
10092            }
10093        "
10094        .unindent(),
10095    );
10096    cx.editor(|editor, _, _| {
10097        assert!(editor.signature_help_state.task().is_none());
10098    });
10099
10100    let mocked_response = lsp::SignatureHelp {
10101        signatures: vec![lsp::SignatureInformation {
10102            label: "fn sample(param1: u8, param2: u8)".to_string(),
10103            documentation: None,
10104            parameters: Some(vec![
10105                lsp::ParameterInformation {
10106                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10107                    documentation: None,
10108                },
10109                lsp::ParameterInformation {
10110                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10111                    documentation: None,
10112                },
10113            ]),
10114            active_parameter: None,
10115        }],
10116        active_signature: Some(0),
10117        active_parameter: Some(0),
10118    };
10119
10120    // Ensure that signature_help is called when enabled afte edits
10121    cx.update(|_, cx| {
10122        cx.update_global::<SettingsStore, _>(|settings, cx| {
10123            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10124                settings.auto_signature_help = Some(false);
10125                settings.show_signature_help_after_edits = Some(true);
10126            });
10127        });
10128    });
10129    cx.set_state(
10130        &r#"
10131            fn main() {
10132                sampleˇ
10133            }
10134        "#
10135        .unindent(),
10136    );
10137    cx.update_editor(|editor, window, cx| {
10138        editor.handle_input("(", window, cx);
10139    });
10140    cx.assert_editor_state(
10141        &"
10142            fn main() {
10143                sample(ˇ)
10144            }
10145        "
10146        .unindent(),
10147    );
10148    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10149    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10150        .await;
10151    cx.update_editor(|editor, _, _| {
10152        let signature_help_state = editor.signature_help_state.popover().cloned();
10153        assert!(signature_help_state.is_some());
10154        assert_eq!(
10155            signature_help_state.unwrap().label,
10156            "param1: u8, param2: u8"
10157        );
10158        editor.signature_help_state = SignatureHelpState::default();
10159    });
10160
10161    // Ensure that signature_help is called when auto signature help override is enabled
10162    cx.update(|_, cx| {
10163        cx.update_global::<SettingsStore, _>(|settings, cx| {
10164            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10165                settings.auto_signature_help = Some(true);
10166                settings.show_signature_help_after_edits = Some(false);
10167            });
10168        });
10169    });
10170    cx.set_state(
10171        &r#"
10172            fn main() {
10173                sampleˇ
10174            }
10175        "#
10176        .unindent(),
10177    );
10178    cx.update_editor(|editor, window, cx| {
10179        editor.handle_input("(", window, cx);
10180    });
10181    cx.assert_editor_state(
10182        &"
10183            fn main() {
10184                sample(ˇ)
10185            }
10186        "
10187        .unindent(),
10188    );
10189    handle_signature_help_request(&mut cx, mocked_response).await;
10190    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10191        .await;
10192    cx.editor(|editor, _, _| {
10193        let signature_help_state = editor.signature_help_state.popover().cloned();
10194        assert!(signature_help_state.is_some());
10195        assert_eq!(
10196            signature_help_state.unwrap().label,
10197            "param1: u8, param2: u8"
10198        );
10199    });
10200}
10201
10202#[gpui::test]
10203async fn test_signature_help(cx: &mut TestAppContext) {
10204    init_test(cx, |_| {});
10205    cx.update(|cx| {
10206        cx.update_global::<SettingsStore, _>(|settings, cx| {
10207            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10208                settings.auto_signature_help = Some(true);
10209            });
10210        });
10211    });
10212
10213    let mut cx = EditorLspTestContext::new_rust(
10214        lsp::ServerCapabilities {
10215            signature_help_provider: Some(lsp::SignatureHelpOptions {
10216                ..Default::default()
10217            }),
10218            ..Default::default()
10219        },
10220        cx,
10221    )
10222    .await;
10223
10224    // A test that directly calls `show_signature_help`
10225    cx.update_editor(|editor, window, cx| {
10226        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10227    });
10228
10229    let mocked_response = lsp::SignatureHelp {
10230        signatures: vec![lsp::SignatureInformation {
10231            label: "fn sample(param1: u8, param2: u8)".to_string(),
10232            documentation: None,
10233            parameters: Some(vec![
10234                lsp::ParameterInformation {
10235                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10236                    documentation: None,
10237                },
10238                lsp::ParameterInformation {
10239                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10240                    documentation: None,
10241                },
10242            ]),
10243            active_parameter: None,
10244        }],
10245        active_signature: Some(0),
10246        active_parameter: Some(0),
10247    };
10248    handle_signature_help_request(&mut cx, mocked_response).await;
10249
10250    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10251        .await;
10252
10253    cx.editor(|editor, _, _| {
10254        let signature_help_state = editor.signature_help_state.popover().cloned();
10255        assert!(signature_help_state.is_some());
10256        assert_eq!(
10257            signature_help_state.unwrap().label,
10258            "param1: u8, param2: u8"
10259        );
10260    });
10261
10262    // When exiting outside from inside the brackets, `signature_help` is closed.
10263    cx.set_state(indoc! {"
10264        fn main() {
10265            sample(ˇ);
10266        }
10267
10268        fn sample(param1: u8, param2: u8) {}
10269    "});
10270
10271    cx.update_editor(|editor, window, cx| {
10272        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10273    });
10274
10275    let mocked_response = lsp::SignatureHelp {
10276        signatures: Vec::new(),
10277        active_signature: None,
10278        active_parameter: None,
10279    };
10280    handle_signature_help_request(&mut cx, mocked_response).await;
10281
10282    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10283        .await;
10284
10285    cx.editor(|editor, _, _| {
10286        assert!(!editor.signature_help_state.is_shown());
10287    });
10288
10289    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10290    cx.set_state(indoc! {"
10291        fn main() {
10292            sample(ˇ);
10293        }
10294
10295        fn sample(param1: u8, param2: u8) {}
10296    "});
10297
10298    let mocked_response = lsp::SignatureHelp {
10299        signatures: vec![lsp::SignatureInformation {
10300            label: "fn sample(param1: u8, param2: u8)".to_string(),
10301            documentation: None,
10302            parameters: Some(vec![
10303                lsp::ParameterInformation {
10304                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10305                    documentation: None,
10306                },
10307                lsp::ParameterInformation {
10308                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10309                    documentation: None,
10310                },
10311            ]),
10312            active_parameter: None,
10313        }],
10314        active_signature: Some(0),
10315        active_parameter: Some(0),
10316    };
10317    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10318    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10319        .await;
10320    cx.editor(|editor, _, _| {
10321        assert!(editor.signature_help_state.is_shown());
10322    });
10323
10324    // Restore the popover with more parameter input
10325    cx.set_state(indoc! {"
10326        fn main() {
10327            sample(param1, param2ˇ);
10328        }
10329
10330        fn sample(param1: u8, param2: u8) {}
10331    "});
10332
10333    let mocked_response = lsp::SignatureHelp {
10334        signatures: vec![lsp::SignatureInformation {
10335            label: "fn sample(param1: u8, param2: u8)".to_string(),
10336            documentation: None,
10337            parameters: Some(vec![
10338                lsp::ParameterInformation {
10339                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10340                    documentation: None,
10341                },
10342                lsp::ParameterInformation {
10343                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10344                    documentation: None,
10345                },
10346            ]),
10347            active_parameter: None,
10348        }],
10349        active_signature: Some(0),
10350        active_parameter: Some(1),
10351    };
10352    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10353    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10354        .await;
10355
10356    // When selecting a range, the popover is gone.
10357    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10358    cx.update_editor(|editor, window, cx| {
10359        editor.change_selections(None, window, cx, |s| {
10360            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10361        })
10362    });
10363    cx.assert_editor_state(indoc! {"
10364        fn main() {
10365            sample(param1, «ˇparam2»);
10366        }
10367
10368        fn sample(param1: u8, param2: u8) {}
10369    "});
10370    cx.editor(|editor, _, _| {
10371        assert!(!editor.signature_help_state.is_shown());
10372    });
10373
10374    // When unselecting again, the popover is back if within the brackets.
10375    cx.update_editor(|editor, window, cx| {
10376        editor.change_selections(None, window, cx, |s| {
10377            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10378        })
10379    });
10380    cx.assert_editor_state(indoc! {"
10381        fn main() {
10382            sample(param1, ˇparam2);
10383        }
10384
10385        fn sample(param1: u8, param2: u8) {}
10386    "});
10387    handle_signature_help_request(&mut cx, mocked_response).await;
10388    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10389        .await;
10390    cx.editor(|editor, _, _| {
10391        assert!(editor.signature_help_state.is_shown());
10392    });
10393
10394    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10395    cx.update_editor(|editor, window, cx| {
10396        editor.change_selections(None, window, cx, |s| {
10397            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10398            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10399        })
10400    });
10401    cx.assert_editor_state(indoc! {"
10402        fn main() {
10403            sample(param1, ˇparam2);
10404        }
10405
10406        fn sample(param1: u8, param2: u8) {}
10407    "});
10408
10409    let mocked_response = lsp::SignatureHelp {
10410        signatures: vec![lsp::SignatureInformation {
10411            label: "fn sample(param1: u8, param2: u8)".to_string(),
10412            documentation: None,
10413            parameters: Some(vec![
10414                lsp::ParameterInformation {
10415                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10416                    documentation: None,
10417                },
10418                lsp::ParameterInformation {
10419                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10420                    documentation: None,
10421                },
10422            ]),
10423            active_parameter: None,
10424        }],
10425        active_signature: Some(0),
10426        active_parameter: Some(1),
10427    };
10428    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10429    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10430        .await;
10431    cx.update_editor(|editor, _, cx| {
10432        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
10433    });
10434    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10435        .await;
10436    cx.update_editor(|editor, window, cx| {
10437        editor.change_selections(None, window, cx, |s| {
10438            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10439        })
10440    });
10441    cx.assert_editor_state(indoc! {"
10442        fn main() {
10443            sample(param1, «ˇparam2»);
10444        }
10445
10446        fn sample(param1: u8, param2: u8) {}
10447    "});
10448    cx.update_editor(|editor, window, cx| {
10449        editor.change_selections(None, window, cx, |s| {
10450            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10451        })
10452    });
10453    cx.assert_editor_state(indoc! {"
10454        fn main() {
10455            sample(param1, ˇparam2);
10456        }
10457
10458        fn sample(param1: u8, param2: u8) {}
10459    "});
10460    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
10461        .await;
10462}
10463
10464#[gpui::test]
10465async fn test_completion_mode(cx: &mut TestAppContext) {
10466    init_test(cx, |_| {});
10467    let mut cx = EditorLspTestContext::new_rust(
10468        lsp::ServerCapabilities {
10469            completion_provider: Some(lsp::CompletionOptions {
10470                resolve_provider: Some(true),
10471                ..Default::default()
10472            }),
10473            ..Default::default()
10474        },
10475        cx,
10476    )
10477    .await;
10478
10479    struct Run {
10480        run_description: &'static str,
10481        initial_state: String,
10482        buffer_marked_text: String,
10483        completion_text: &'static str,
10484        expected_with_insert_mode: String,
10485        expected_with_replace_mode: String,
10486        expected_with_replace_subsequence_mode: String,
10487        expected_with_replace_suffix_mode: String,
10488    }
10489
10490    let runs = [
10491        Run {
10492            run_description: "Start of word matches completion text",
10493            initial_state: "before ediˇ after".into(),
10494            buffer_marked_text: "before <edi|> after".into(),
10495            completion_text: "editor",
10496            expected_with_insert_mode: "before editorˇ after".into(),
10497            expected_with_replace_mode: "before editorˇ after".into(),
10498            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10499            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10500        },
10501        Run {
10502            run_description: "Accept same text at the middle of the word",
10503            initial_state: "before ediˇtor after".into(),
10504            buffer_marked_text: "before <edi|tor> after".into(),
10505            completion_text: "editor",
10506            expected_with_insert_mode: "before editorˇtor after".into(),
10507            expected_with_replace_mode: "before editorˇ after".into(),
10508            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10509            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10510        },
10511        Run {
10512            run_description: "End of word matches completion text -- cursor at end",
10513            initial_state: "before torˇ after".into(),
10514            buffer_marked_text: "before <tor|> after".into(),
10515            completion_text: "editor",
10516            expected_with_insert_mode: "before editorˇ after".into(),
10517            expected_with_replace_mode: "before editorˇ after".into(),
10518            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10519            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10520        },
10521        Run {
10522            run_description: "End of word matches completion text -- cursor at start",
10523            initial_state: "before ˇtor after".into(),
10524            buffer_marked_text: "before <|tor> after".into(),
10525            completion_text: "editor",
10526            expected_with_insert_mode: "before editorˇtor after".into(),
10527            expected_with_replace_mode: "before editorˇ after".into(),
10528            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
10529            expected_with_replace_suffix_mode: "before editorˇ after".into(),
10530        },
10531        Run {
10532            run_description: "Prepend text containing whitespace",
10533            initial_state: "pˇfield: bool".into(),
10534            buffer_marked_text: "<p|field>: bool".into(),
10535            completion_text: "pub ",
10536            expected_with_insert_mode: "pub ˇfield: bool".into(),
10537            expected_with_replace_mode: "pub ˇ: bool".into(),
10538            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
10539            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
10540        },
10541        Run {
10542            run_description: "Add element to start of list",
10543            initial_state: "[element_ˇelement_2]".into(),
10544            buffer_marked_text: "[<element_|element_2>]".into(),
10545            completion_text: "element_1",
10546            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
10547            expected_with_replace_mode: "[element_1ˇ]".into(),
10548            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
10549            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
10550        },
10551        Run {
10552            run_description: "Add element to start of list -- first and second elements are equal",
10553            initial_state: "[elˇelement]".into(),
10554            buffer_marked_text: "[<el|element>]".into(),
10555            completion_text: "element",
10556            expected_with_insert_mode: "[elementˇelement]".into(),
10557            expected_with_replace_mode: "[elementˇ]".into(),
10558            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
10559            expected_with_replace_suffix_mode: "[elementˇ]".into(),
10560        },
10561        Run {
10562            run_description: "Ends with matching suffix",
10563            initial_state: "SubˇError".into(),
10564            buffer_marked_text: "<Sub|Error>".into(),
10565            completion_text: "SubscriptionError",
10566            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
10567            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10568            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10569            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
10570        },
10571        Run {
10572            run_description: "Suffix is a subsequence -- contiguous",
10573            initial_state: "SubˇErr".into(),
10574            buffer_marked_text: "<Sub|Err>".into(),
10575            completion_text: "SubscriptionError",
10576            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
10577            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10578            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10579            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
10580        },
10581        Run {
10582            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
10583            initial_state: "Suˇscrirr".into(),
10584            buffer_marked_text: "<Su|scrirr>".into(),
10585            completion_text: "SubscriptionError",
10586            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
10587            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
10588            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
10589            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
10590        },
10591        Run {
10592            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
10593            initial_state: "foo(indˇix)".into(),
10594            buffer_marked_text: "foo(<ind|ix>)".into(),
10595            completion_text: "node_index",
10596            expected_with_insert_mode: "foo(node_indexˇix)".into(),
10597            expected_with_replace_mode: "foo(node_indexˇ)".into(),
10598            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
10599            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
10600        },
10601    ];
10602
10603    for run in runs {
10604        let run_variations = [
10605            (LspInsertMode::Insert, run.expected_with_insert_mode),
10606            (LspInsertMode::Replace, run.expected_with_replace_mode),
10607            (
10608                LspInsertMode::ReplaceSubsequence,
10609                run.expected_with_replace_subsequence_mode,
10610            ),
10611            (
10612                LspInsertMode::ReplaceSuffix,
10613                run.expected_with_replace_suffix_mode,
10614            ),
10615        ];
10616
10617        for (lsp_insert_mode, expected_text) in run_variations {
10618            eprintln!(
10619                "run = {:?}, mode = {lsp_insert_mode:.?}",
10620                run.run_description,
10621            );
10622
10623            update_test_language_settings(&mut cx, |settings| {
10624                settings.defaults.completions = Some(CompletionSettings {
10625                    lsp_insert_mode,
10626                    words: WordsCompletionMode::Disabled,
10627                    lsp: true,
10628                    lsp_fetch_timeout_ms: 0,
10629                });
10630            });
10631
10632            cx.set_state(&run.initial_state);
10633            cx.update_editor(|editor, window, cx| {
10634                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10635            });
10636
10637            let counter = Arc::new(AtomicUsize::new(0));
10638            handle_completion_request_with_insert_and_replace(
10639                &mut cx,
10640                &run.buffer_marked_text,
10641                vec![run.completion_text],
10642                counter.clone(),
10643            )
10644            .await;
10645            cx.condition(|editor, _| editor.context_menu_visible())
10646                .await;
10647            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10648
10649            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10650                editor
10651                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
10652                    .unwrap()
10653            });
10654            cx.assert_editor_state(&expected_text);
10655            handle_resolve_completion_request(&mut cx, None).await;
10656            apply_additional_edits.await.unwrap();
10657        }
10658    }
10659}
10660
10661#[gpui::test]
10662async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
10663    init_test(cx, |_| {});
10664    let mut cx = EditorLspTestContext::new_rust(
10665        lsp::ServerCapabilities {
10666            completion_provider: Some(lsp::CompletionOptions {
10667                resolve_provider: Some(true),
10668                ..Default::default()
10669            }),
10670            ..Default::default()
10671        },
10672        cx,
10673    )
10674    .await;
10675
10676    let initial_state = "SubˇError";
10677    let buffer_marked_text = "<Sub|Error>";
10678    let completion_text = "SubscriptionError";
10679    let expected_with_insert_mode = "SubscriptionErrorˇError";
10680    let expected_with_replace_mode = "SubscriptionErrorˇ";
10681
10682    update_test_language_settings(&mut cx, |settings| {
10683        settings.defaults.completions = Some(CompletionSettings {
10684            words: WordsCompletionMode::Disabled,
10685            // set the opposite here to ensure that the action is overriding the default behavior
10686            lsp_insert_mode: LspInsertMode::Insert,
10687            lsp: true,
10688            lsp_fetch_timeout_ms: 0,
10689        });
10690    });
10691
10692    cx.set_state(initial_state);
10693    cx.update_editor(|editor, window, cx| {
10694        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10695    });
10696
10697    let counter = Arc::new(AtomicUsize::new(0));
10698    handle_completion_request_with_insert_and_replace(
10699        &mut cx,
10700        &buffer_marked_text,
10701        vec![completion_text],
10702        counter.clone(),
10703    )
10704    .await;
10705    cx.condition(|editor, _| editor.context_menu_visible())
10706        .await;
10707    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10708
10709    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10710        editor
10711            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10712            .unwrap()
10713    });
10714    cx.assert_editor_state(&expected_with_replace_mode);
10715    handle_resolve_completion_request(&mut cx, None).await;
10716    apply_additional_edits.await.unwrap();
10717
10718    update_test_language_settings(&mut cx, |settings| {
10719        settings.defaults.completions = Some(CompletionSettings {
10720            words: WordsCompletionMode::Disabled,
10721            // set the opposite here to ensure that the action is overriding the default behavior
10722            lsp_insert_mode: LspInsertMode::Replace,
10723            lsp: true,
10724            lsp_fetch_timeout_ms: 0,
10725        });
10726    });
10727
10728    cx.set_state(initial_state);
10729    cx.update_editor(|editor, window, cx| {
10730        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10731    });
10732    handle_completion_request_with_insert_and_replace(
10733        &mut cx,
10734        &buffer_marked_text,
10735        vec![completion_text],
10736        counter.clone(),
10737    )
10738    .await;
10739    cx.condition(|editor, _| editor.context_menu_visible())
10740        .await;
10741    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
10742
10743    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10744        editor
10745            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
10746            .unwrap()
10747    });
10748    cx.assert_editor_state(&expected_with_insert_mode);
10749    handle_resolve_completion_request(&mut cx, None).await;
10750    apply_additional_edits.await.unwrap();
10751}
10752
10753#[gpui::test]
10754async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
10755    init_test(cx, |_| {});
10756    let mut cx = EditorLspTestContext::new_rust(
10757        lsp::ServerCapabilities {
10758            completion_provider: Some(lsp::CompletionOptions {
10759                resolve_provider: Some(true),
10760                ..Default::default()
10761            }),
10762            ..Default::default()
10763        },
10764        cx,
10765    )
10766    .await;
10767
10768    // scenario: surrounding text matches completion text
10769    let completion_text = "to_offset";
10770    let initial_state = indoc! {"
10771        1. buf.to_offˇsuffix
10772        2. buf.to_offˇsuf
10773        3. buf.to_offˇfix
10774        4. buf.to_offˇ
10775        5. into_offˇensive
10776        6. ˇsuffix
10777        7. let ˇ //
10778        8. aaˇzz
10779        9. buf.to_off«zzzzzˇ»suffix
10780        10. buf.«ˇzzzzz»suffix
10781        11. to_off«ˇzzzzz»
10782
10783        buf.to_offˇsuffix  // newest cursor
10784    "};
10785    let completion_marked_buffer = indoc! {"
10786        1. buf.to_offsuffix
10787        2. buf.to_offsuf
10788        3. buf.to_offfix
10789        4. buf.to_off
10790        5. into_offensive
10791        6. suffix
10792        7. let  //
10793        8. aazz
10794        9. buf.to_offzzzzzsuffix
10795        10. buf.zzzzzsuffix
10796        11. to_offzzzzz
10797
10798        buf.<to_off|suffix>  // newest cursor
10799    "};
10800    let expected = indoc! {"
10801        1. buf.to_offsetˇ
10802        2. buf.to_offsetˇsuf
10803        3. buf.to_offsetˇfix
10804        4. buf.to_offsetˇ
10805        5. into_offsetˇensive
10806        6. to_offsetˇsuffix
10807        7. let to_offsetˇ //
10808        8. aato_offsetˇzz
10809        9. buf.to_offsetˇ
10810        10. buf.to_offsetˇsuffix
10811        11. to_offsetˇ
10812
10813        buf.to_offsetˇ  // newest cursor
10814    "};
10815    cx.set_state(initial_state);
10816    cx.update_editor(|editor, window, cx| {
10817        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10818    });
10819    handle_completion_request_with_insert_and_replace(
10820        &mut cx,
10821        completion_marked_buffer,
10822        vec![completion_text],
10823        Arc::new(AtomicUsize::new(0)),
10824    )
10825    .await;
10826    cx.condition(|editor, _| editor.context_menu_visible())
10827        .await;
10828    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10829        editor
10830            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10831            .unwrap()
10832    });
10833    cx.assert_editor_state(expected);
10834    handle_resolve_completion_request(&mut cx, None).await;
10835    apply_additional_edits.await.unwrap();
10836
10837    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
10838    let completion_text = "foo_and_bar";
10839    let initial_state = indoc! {"
10840        1. ooanbˇ
10841        2. zooanbˇ
10842        3. ooanbˇz
10843        4. zooanbˇz
10844        5. ooanˇ
10845        6. oanbˇ
10846
10847        ooanbˇ
10848    "};
10849    let completion_marked_buffer = indoc! {"
10850        1. ooanb
10851        2. zooanb
10852        3. ooanbz
10853        4. zooanbz
10854        5. ooan
10855        6. oanb
10856
10857        <ooanb|>
10858    "};
10859    let expected = indoc! {"
10860        1. foo_and_barˇ
10861        2. zfoo_and_barˇ
10862        3. foo_and_barˇz
10863        4. zfoo_and_barˇz
10864        5. ooanfoo_and_barˇ
10865        6. oanbfoo_and_barˇ
10866
10867        foo_and_barˇ
10868    "};
10869    cx.set_state(initial_state);
10870    cx.update_editor(|editor, window, cx| {
10871        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10872    });
10873    handle_completion_request_with_insert_and_replace(
10874        &mut cx,
10875        completion_marked_buffer,
10876        vec![completion_text],
10877        Arc::new(AtomicUsize::new(0)),
10878    )
10879    .await;
10880    cx.condition(|editor, _| editor.context_menu_visible())
10881        .await;
10882    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10883        editor
10884            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10885            .unwrap()
10886    });
10887    cx.assert_editor_state(expected);
10888    handle_resolve_completion_request(&mut cx, None).await;
10889    apply_additional_edits.await.unwrap();
10890
10891    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
10892    // (expects the same as if it was inserted at the end)
10893    let completion_text = "foo_and_bar";
10894    let initial_state = indoc! {"
10895        1. ooˇanb
10896        2. zooˇanb
10897        3. ooˇanbz
10898        4. zooˇanbz
10899
10900        ooˇanb
10901    "};
10902    let completion_marked_buffer = indoc! {"
10903        1. ooanb
10904        2. zooanb
10905        3. ooanbz
10906        4. zooanbz
10907
10908        <oo|anb>
10909    "};
10910    let expected = indoc! {"
10911        1. foo_and_barˇ
10912        2. zfoo_and_barˇ
10913        3. foo_and_barˇz
10914        4. zfoo_and_barˇz
10915
10916        foo_and_barˇ
10917    "};
10918    cx.set_state(initial_state);
10919    cx.update_editor(|editor, window, cx| {
10920        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
10921    });
10922    handle_completion_request_with_insert_and_replace(
10923        &mut cx,
10924        completion_marked_buffer,
10925        vec![completion_text],
10926        Arc::new(AtomicUsize::new(0)),
10927    )
10928    .await;
10929    cx.condition(|editor, _| editor.context_menu_visible())
10930        .await;
10931    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
10932        editor
10933            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
10934            .unwrap()
10935    });
10936    cx.assert_editor_state(expected);
10937    handle_resolve_completion_request(&mut cx, None).await;
10938    apply_additional_edits.await.unwrap();
10939}
10940
10941// This used to crash
10942#[gpui::test]
10943async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
10944    init_test(cx, |_| {});
10945
10946    let buffer_text = indoc! {"
10947        fn main() {
10948            10.satu;
10949
10950            //
10951            // separate cursors so they open in different excerpts (manually reproducible)
10952            //
10953
10954            10.satu20;
10955        }
10956    "};
10957    let multibuffer_text_with_selections = indoc! {"
10958        fn main() {
10959            10.satuˇ;
10960
10961            //
10962
10963            //
10964
10965            10.satuˇ20;
10966        }
10967    "};
10968    let expected_multibuffer = indoc! {"
10969        fn main() {
10970            10.saturating_sub()ˇ;
10971
10972            //
10973
10974            //
10975
10976            10.saturating_sub()ˇ;
10977        }
10978    "};
10979
10980    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
10981    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
10982
10983    let fs = FakeFs::new(cx.executor());
10984    fs.insert_tree(
10985        path!("/a"),
10986        json!({
10987            "main.rs": buffer_text,
10988        }),
10989    )
10990    .await;
10991
10992    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10993    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10994    language_registry.add(rust_lang());
10995    let mut fake_servers = language_registry.register_fake_lsp(
10996        "Rust",
10997        FakeLspAdapter {
10998            capabilities: lsp::ServerCapabilities {
10999                completion_provider: Some(lsp::CompletionOptions {
11000                    resolve_provider: None,
11001                    ..lsp::CompletionOptions::default()
11002                }),
11003                ..lsp::ServerCapabilities::default()
11004            },
11005            ..FakeLspAdapter::default()
11006        },
11007    );
11008    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11009    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11010    let buffer = project
11011        .update(cx, |project, cx| {
11012            project.open_local_buffer(path!("/a/main.rs"), cx)
11013        })
11014        .await
11015        .unwrap();
11016
11017    let multi_buffer = cx.new(|cx| {
11018        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11019        multi_buffer.push_excerpts(
11020            buffer.clone(),
11021            [ExcerptRange::new(0..first_excerpt_end)],
11022            cx,
11023        );
11024        multi_buffer.push_excerpts(
11025            buffer.clone(),
11026            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11027            cx,
11028        );
11029        multi_buffer
11030    });
11031
11032    let editor = workspace
11033        .update(cx, |_, window, cx| {
11034            cx.new(|cx| {
11035                Editor::new(
11036                    EditorMode::Full {
11037                        scale_ui_elements_with_buffer_font_size: false,
11038                        show_active_line_background: false,
11039                        sized_by_content: false,
11040                    },
11041                    multi_buffer.clone(),
11042                    Some(project.clone()),
11043                    window,
11044                    cx,
11045                )
11046            })
11047        })
11048        .unwrap();
11049
11050    let pane = workspace
11051        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11052        .unwrap();
11053    pane.update_in(cx, |pane, window, cx| {
11054        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11055    });
11056
11057    let fake_server = fake_servers.next().await.unwrap();
11058
11059    editor.update_in(cx, |editor, window, cx| {
11060        editor.change_selections(None, window, cx, |s| {
11061            s.select_ranges([
11062                Point::new(1, 11)..Point::new(1, 11),
11063                Point::new(7, 11)..Point::new(7, 11),
11064            ])
11065        });
11066
11067        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11068    });
11069
11070    editor.update_in(cx, |editor, window, cx| {
11071        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11072    });
11073
11074    fake_server
11075        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11076            let completion_item = lsp::CompletionItem {
11077                label: "saturating_sub()".into(),
11078                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11079                    lsp::InsertReplaceEdit {
11080                        new_text: "saturating_sub()".to_owned(),
11081                        insert: lsp::Range::new(
11082                            lsp::Position::new(7, 7),
11083                            lsp::Position::new(7, 11),
11084                        ),
11085                        replace: lsp::Range::new(
11086                            lsp::Position::new(7, 7),
11087                            lsp::Position::new(7, 13),
11088                        ),
11089                    },
11090                )),
11091                ..lsp::CompletionItem::default()
11092            };
11093
11094            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11095        })
11096        .next()
11097        .await
11098        .unwrap();
11099
11100    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11101        .await;
11102
11103    editor
11104        .update_in(cx, |editor, window, cx| {
11105            editor
11106                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11107                .unwrap()
11108        })
11109        .await
11110        .unwrap();
11111
11112    editor.update(cx, |editor, cx| {
11113        assert_text_with_selections(editor, expected_multibuffer, cx);
11114    })
11115}
11116
11117#[gpui::test]
11118async fn test_completion(cx: &mut TestAppContext) {
11119    init_test(cx, |_| {});
11120
11121    let mut cx = EditorLspTestContext::new_rust(
11122        lsp::ServerCapabilities {
11123            completion_provider: Some(lsp::CompletionOptions {
11124                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11125                resolve_provider: Some(true),
11126                ..Default::default()
11127            }),
11128            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11129            ..Default::default()
11130        },
11131        cx,
11132    )
11133    .await;
11134    let counter = Arc::new(AtomicUsize::new(0));
11135
11136    cx.set_state(indoc! {"
11137        oneˇ
11138        two
11139        three
11140    "});
11141    cx.simulate_keystroke(".");
11142    handle_completion_request(
11143        &mut cx,
11144        indoc! {"
11145            one.|<>
11146            two
11147            three
11148        "},
11149        vec!["first_completion", "second_completion"],
11150        counter.clone(),
11151    )
11152    .await;
11153    cx.condition(|editor, _| editor.context_menu_visible())
11154        .await;
11155    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11156
11157    let _handler = handle_signature_help_request(
11158        &mut cx,
11159        lsp::SignatureHelp {
11160            signatures: vec![lsp::SignatureInformation {
11161                label: "test signature".to_string(),
11162                documentation: None,
11163                parameters: Some(vec![lsp::ParameterInformation {
11164                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11165                    documentation: None,
11166                }]),
11167                active_parameter: None,
11168            }],
11169            active_signature: None,
11170            active_parameter: None,
11171        },
11172    );
11173    cx.update_editor(|editor, window, cx| {
11174        assert!(
11175            !editor.signature_help_state.is_shown(),
11176            "No signature help was called for"
11177        );
11178        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11179    });
11180    cx.run_until_parked();
11181    cx.update_editor(|editor, _, _| {
11182        assert!(
11183            !editor.signature_help_state.is_shown(),
11184            "No signature help should be shown when completions menu is open"
11185        );
11186    });
11187
11188    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11189        editor.context_menu_next(&Default::default(), window, cx);
11190        editor
11191            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11192            .unwrap()
11193    });
11194    cx.assert_editor_state(indoc! {"
11195        one.second_completionˇ
11196        two
11197        three
11198    "});
11199
11200    handle_resolve_completion_request(
11201        &mut cx,
11202        Some(vec![
11203            (
11204                //This overlaps with the primary completion edit which is
11205                //misbehavior from the LSP spec, test that we filter it out
11206                indoc! {"
11207                    one.second_ˇcompletion
11208                    two
11209                    threeˇ
11210                "},
11211                "overlapping additional edit",
11212            ),
11213            (
11214                indoc! {"
11215                    one.second_completion
11216                    two
11217                    threeˇ
11218                "},
11219                "\nadditional edit",
11220            ),
11221        ]),
11222    )
11223    .await;
11224    apply_additional_edits.await.unwrap();
11225    cx.assert_editor_state(indoc! {"
11226        one.second_completionˇ
11227        two
11228        three
11229        additional edit
11230    "});
11231
11232    cx.set_state(indoc! {"
11233        one.second_completion
11234        twoˇ
11235        threeˇ
11236        additional edit
11237    "});
11238    cx.simulate_keystroke(" ");
11239    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11240    cx.simulate_keystroke("s");
11241    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11242
11243    cx.assert_editor_state(indoc! {"
11244        one.second_completion
11245        two sˇ
11246        three sˇ
11247        additional edit
11248    "});
11249    handle_completion_request(
11250        &mut cx,
11251        indoc! {"
11252            one.second_completion
11253            two s
11254            three <s|>
11255            additional edit
11256        "},
11257        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11258        counter.clone(),
11259    )
11260    .await;
11261    cx.condition(|editor, _| editor.context_menu_visible())
11262        .await;
11263    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11264
11265    cx.simulate_keystroke("i");
11266
11267    handle_completion_request(
11268        &mut cx,
11269        indoc! {"
11270            one.second_completion
11271            two si
11272            three <si|>
11273            additional edit
11274        "},
11275        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11276        counter.clone(),
11277    )
11278    .await;
11279    cx.condition(|editor, _| editor.context_menu_visible())
11280        .await;
11281    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11282
11283    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11284        editor
11285            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11286            .unwrap()
11287    });
11288    cx.assert_editor_state(indoc! {"
11289        one.second_completion
11290        two sixth_completionˇ
11291        three sixth_completionˇ
11292        additional edit
11293    "});
11294
11295    apply_additional_edits.await.unwrap();
11296
11297    update_test_language_settings(&mut cx, |settings| {
11298        settings.defaults.show_completions_on_input = Some(false);
11299    });
11300    cx.set_state("editorˇ");
11301    cx.simulate_keystroke(".");
11302    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11303    cx.simulate_keystrokes("c l o");
11304    cx.assert_editor_state("editor.cloˇ");
11305    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11306    cx.update_editor(|editor, window, cx| {
11307        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11308    });
11309    handle_completion_request(
11310        &mut cx,
11311        "editor.<clo|>",
11312        vec!["close", "clobber"],
11313        counter.clone(),
11314    )
11315    .await;
11316    cx.condition(|editor, _| editor.context_menu_visible())
11317        .await;
11318    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11319
11320    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11321        editor
11322            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11323            .unwrap()
11324    });
11325    cx.assert_editor_state("editor.closeˇ");
11326    handle_resolve_completion_request(&mut cx, None).await;
11327    apply_additional_edits.await.unwrap();
11328}
11329
11330#[gpui::test]
11331async fn test_word_completion(cx: &mut TestAppContext) {
11332    let lsp_fetch_timeout_ms = 10;
11333    init_test(cx, |language_settings| {
11334        language_settings.defaults.completions = Some(CompletionSettings {
11335            words: WordsCompletionMode::Fallback,
11336            lsp: true,
11337            lsp_fetch_timeout_ms: 10,
11338            lsp_insert_mode: LspInsertMode::Insert,
11339        });
11340    });
11341
11342    let mut cx = EditorLspTestContext::new_rust(
11343        lsp::ServerCapabilities {
11344            completion_provider: Some(lsp::CompletionOptions {
11345                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11346                ..lsp::CompletionOptions::default()
11347            }),
11348            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11349            ..lsp::ServerCapabilities::default()
11350        },
11351        cx,
11352    )
11353    .await;
11354
11355    let throttle_completions = Arc::new(AtomicBool::new(false));
11356
11357    let lsp_throttle_completions = throttle_completions.clone();
11358    let _completion_requests_handler =
11359        cx.lsp
11360            .server
11361            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
11362                let lsp_throttle_completions = lsp_throttle_completions.clone();
11363                let cx = cx.clone();
11364                async move {
11365                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
11366                        cx.background_executor()
11367                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
11368                            .await;
11369                    }
11370                    Ok(Some(lsp::CompletionResponse::Array(vec![
11371                        lsp::CompletionItem {
11372                            label: "first".into(),
11373                            ..lsp::CompletionItem::default()
11374                        },
11375                        lsp::CompletionItem {
11376                            label: "last".into(),
11377                            ..lsp::CompletionItem::default()
11378                        },
11379                    ])))
11380                }
11381            });
11382
11383    cx.set_state(indoc! {"
11384        oneˇ
11385        two
11386        three
11387    "});
11388    cx.simulate_keystroke(".");
11389    cx.executor().run_until_parked();
11390    cx.condition(|editor, _| editor.context_menu_visible())
11391        .await;
11392    cx.update_editor(|editor, window, cx| {
11393        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11394        {
11395            assert_eq!(
11396                completion_menu_entries(&menu),
11397                &["first", "last"],
11398                "When LSP server is fast to reply, no fallback word completions are used"
11399            );
11400        } else {
11401            panic!("expected completion menu to be open");
11402        }
11403        editor.cancel(&Cancel, window, cx);
11404    });
11405    cx.executor().run_until_parked();
11406    cx.condition(|editor, _| !editor.context_menu_visible())
11407        .await;
11408
11409    throttle_completions.store(true, atomic::Ordering::Release);
11410    cx.simulate_keystroke(".");
11411    cx.executor()
11412        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
11413    cx.executor().run_until_parked();
11414    cx.condition(|editor, _| editor.context_menu_visible())
11415        .await;
11416    cx.update_editor(|editor, _, _| {
11417        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11418        {
11419            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
11420                "When LSP server is slow, document words can be shown instead, if configured accordingly");
11421        } else {
11422            panic!("expected completion menu to be open");
11423        }
11424    });
11425}
11426
11427#[gpui::test]
11428async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
11429    init_test(cx, |language_settings| {
11430        language_settings.defaults.completions = Some(CompletionSettings {
11431            words: WordsCompletionMode::Enabled,
11432            lsp: true,
11433            lsp_fetch_timeout_ms: 0,
11434            lsp_insert_mode: LspInsertMode::Insert,
11435        });
11436    });
11437
11438    let mut cx = EditorLspTestContext::new_rust(
11439        lsp::ServerCapabilities {
11440            completion_provider: Some(lsp::CompletionOptions {
11441                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11442                ..lsp::CompletionOptions::default()
11443            }),
11444            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11445            ..lsp::ServerCapabilities::default()
11446        },
11447        cx,
11448    )
11449    .await;
11450
11451    let _completion_requests_handler =
11452        cx.lsp
11453            .server
11454            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11455                Ok(Some(lsp::CompletionResponse::Array(vec![
11456                    lsp::CompletionItem {
11457                        label: "first".into(),
11458                        ..lsp::CompletionItem::default()
11459                    },
11460                    lsp::CompletionItem {
11461                        label: "last".into(),
11462                        ..lsp::CompletionItem::default()
11463                    },
11464                ])))
11465            });
11466
11467    cx.set_state(indoc! {"ˇ
11468        first
11469        last
11470        second
11471    "});
11472    cx.simulate_keystroke(".");
11473    cx.executor().run_until_parked();
11474    cx.condition(|editor, _| editor.context_menu_visible())
11475        .await;
11476    cx.update_editor(|editor, _, _| {
11477        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11478        {
11479            assert_eq!(
11480                completion_menu_entries(&menu),
11481                &["first", "last", "second"],
11482                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
11483            );
11484        } else {
11485            panic!("expected completion menu to be open");
11486        }
11487    });
11488}
11489
11490#[gpui::test]
11491async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
11492    init_test(cx, |language_settings| {
11493        language_settings.defaults.completions = Some(CompletionSettings {
11494            words: WordsCompletionMode::Disabled,
11495            lsp: true,
11496            lsp_fetch_timeout_ms: 0,
11497            lsp_insert_mode: LspInsertMode::Insert,
11498        });
11499    });
11500
11501    let mut cx = EditorLspTestContext::new_rust(
11502        lsp::ServerCapabilities {
11503            completion_provider: Some(lsp::CompletionOptions {
11504                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11505                ..lsp::CompletionOptions::default()
11506            }),
11507            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11508            ..lsp::ServerCapabilities::default()
11509        },
11510        cx,
11511    )
11512    .await;
11513
11514    let _completion_requests_handler =
11515        cx.lsp
11516            .server
11517            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11518                panic!("LSP completions should not be queried when dealing with word completions")
11519            });
11520
11521    cx.set_state(indoc! {"ˇ
11522        first
11523        last
11524        second
11525    "});
11526    cx.update_editor(|editor, window, cx| {
11527        editor.show_word_completions(&ShowWordCompletions, window, cx);
11528    });
11529    cx.executor().run_until_parked();
11530    cx.condition(|editor, _| editor.context_menu_visible())
11531        .await;
11532    cx.update_editor(|editor, _, _| {
11533        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11534        {
11535            assert_eq!(
11536                completion_menu_entries(&menu),
11537                &["first", "last", "second"],
11538                "`ShowWordCompletions` action should show word completions"
11539            );
11540        } else {
11541            panic!("expected completion menu to be open");
11542        }
11543    });
11544
11545    cx.simulate_keystroke("l");
11546    cx.executor().run_until_parked();
11547    cx.condition(|editor, _| editor.context_menu_visible())
11548        .await;
11549    cx.update_editor(|editor, _, _| {
11550        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11551        {
11552            assert_eq!(
11553                completion_menu_entries(&menu),
11554                &["last"],
11555                "After showing word completions, further editing should filter them and not query the LSP"
11556            );
11557        } else {
11558            panic!("expected completion menu to be open");
11559        }
11560    });
11561}
11562
11563#[gpui::test]
11564async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
11565    init_test(cx, |language_settings| {
11566        language_settings.defaults.completions = Some(CompletionSettings {
11567            words: WordsCompletionMode::Fallback,
11568            lsp: false,
11569            lsp_fetch_timeout_ms: 0,
11570            lsp_insert_mode: LspInsertMode::Insert,
11571        });
11572    });
11573
11574    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11575
11576    cx.set_state(indoc! {"ˇ
11577        0_usize
11578        let
11579        33
11580        4.5f32
11581    "});
11582    cx.update_editor(|editor, window, cx| {
11583        editor.show_completions(&ShowCompletions::default(), window, cx);
11584    });
11585    cx.executor().run_until_parked();
11586    cx.condition(|editor, _| editor.context_menu_visible())
11587        .await;
11588    cx.update_editor(|editor, window, cx| {
11589        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11590        {
11591            assert_eq!(
11592                completion_menu_entries(&menu),
11593                &["let"],
11594                "With no digits in the completion query, no digits should be in the word completions"
11595            );
11596        } else {
11597            panic!("expected completion menu to be open");
11598        }
11599        editor.cancel(&Cancel, window, cx);
11600    });
11601
11602    cx.set_state(indoc! {"11603        0_usize
11604        let
11605        3
11606        33.35f32
11607    "});
11608    cx.update_editor(|editor, window, cx| {
11609        editor.show_completions(&ShowCompletions::default(), window, cx);
11610    });
11611    cx.executor().run_until_parked();
11612    cx.condition(|editor, _| editor.context_menu_visible())
11613        .await;
11614    cx.update_editor(|editor, _, _| {
11615        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11616        {
11617            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
11618                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
11619        } else {
11620            panic!("expected completion menu to be open");
11621        }
11622    });
11623}
11624
11625fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
11626    let position = || lsp::Position {
11627        line: params.text_document_position.position.line,
11628        character: params.text_document_position.position.character,
11629    };
11630    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11631        range: lsp::Range {
11632            start: position(),
11633            end: position(),
11634        },
11635        new_text: text.to_string(),
11636    }))
11637}
11638
11639#[gpui::test]
11640async fn test_multiline_completion(cx: &mut TestAppContext) {
11641    init_test(cx, |_| {});
11642
11643    let fs = FakeFs::new(cx.executor());
11644    fs.insert_tree(
11645        path!("/a"),
11646        json!({
11647            "main.ts": "a",
11648        }),
11649    )
11650    .await;
11651
11652    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11653    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11654    let typescript_language = Arc::new(Language::new(
11655        LanguageConfig {
11656            name: "TypeScript".into(),
11657            matcher: LanguageMatcher {
11658                path_suffixes: vec!["ts".to_string()],
11659                ..LanguageMatcher::default()
11660            },
11661            line_comments: vec!["// ".into()],
11662            ..LanguageConfig::default()
11663        },
11664        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11665    ));
11666    language_registry.add(typescript_language.clone());
11667    let mut fake_servers = language_registry.register_fake_lsp(
11668        "TypeScript",
11669        FakeLspAdapter {
11670            capabilities: lsp::ServerCapabilities {
11671                completion_provider: Some(lsp::CompletionOptions {
11672                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11673                    ..lsp::CompletionOptions::default()
11674                }),
11675                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11676                ..lsp::ServerCapabilities::default()
11677            },
11678            // Emulate vtsls label generation
11679            label_for_completion: Some(Box::new(|item, _| {
11680                let text = if let Some(description) = item
11681                    .label_details
11682                    .as_ref()
11683                    .and_then(|label_details| label_details.description.as_ref())
11684                {
11685                    format!("{} {}", item.label, description)
11686                } else if let Some(detail) = &item.detail {
11687                    format!("{} {}", item.label, detail)
11688                } else {
11689                    item.label.clone()
11690                };
11691                let len = text.len();
11692                Some(language::CodeLabel {
11693                    text,
11694                    runs: Vec::new(),
11695                    filter_range: 0..len,
11696                })
11697            })),
11698            ..FakeLspAdapter::default()
11699        },
11700    );
11701    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11702    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11703    let worktree_id = workspace
11704        .update(cx, |workspace, _window, cx| {
11705            workspace.project().update(cx, |project, cx| {
11706                project.worktrees(cx).next().unwrap().read(cx).id()
11707            })
11708        })
11709        .unwrap();
11710    let _buffer = project
11711        .update(cx, |project, cx| {
11712            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
11713        })
11714        .await
11715        .unwrap();
11716    let editor = workspace
11717        .update(cx, |workspace, window, cx| {
11718            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
11719        })
11720        .unwrap()
11721        .await
11722        .unwrap()
11723        .downcast::<Editor>()
11724        .unwrap();
11725    let fake_server = fake_servers.next().await.unwrap();
11726
11727    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
11728    let multiline_label_2 = "a\nb\nc\n";
11729    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
11730    let multiline_description = "d\ne\nf\n";
11731    let multiline_detail_2 = "g\nh\ni\n";
11732
11733    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
11734        move |params, _| async move {
11735            Ok(Some(lsp::CompletionResponse::Array(vec![
11736                lsp::CompletionItem {
11737                    label: multiline_label.to_string(),
11738                    text_edit: gen_text_edit(&params, "new_text_1"),
11739                    ..lsp::CompletionItem::default()
11740                },
11741                lsp::CompletionItem {
11742                    label: "single line label 1".to_string(),
11743                    detail: Some(multiline_detail.to_string()),
11744                    text_edit: gen_text_edit(&params, "new_text_2"),
11745                    ..lsp::CompletionItem::default()
11746                },
11747                lsp::CompletionItem {
11748                    label: "single line label 2".to_string(),
11749                    label_details: Some(lsp::CompletionItemLabelDetails {
11750                        description: Some(multiline_description.to_string()),
11751                        detail: None,
11752                    }),
11753                    text_edit: gen_text_edit(&params, "new_text_2"),
11754                    ..lsp::CompletionItem::default()
11755                },
11756                lsp::CompletionItem {
11757                    label: multiline_label_2.to_string(),
11758                    detail: Some(multiline_detail_2.to_string()),
11759                    text_edit: gen_text_edit(&params, "new_text_3"),
11760                    ..lsp::CompletionItem::default()
11761                },
11762                lsp::CompletionItem {
11763                    label: "Label with many     spaces and \t but without newlines".to_string(),
11764                    detail: Some(
11765                        "Details with many     spaces and \t but without newlines".to_string(),
11766                    ),
11767                    text_edit: gen_text_edit(&params, "new_text_4"),
11768                    ..lsp::CompletionItem::default()
11769                },
11770            ])))
11771        },
11772    );
11773
11774    editor.update_in(cx, |editor, window, cx| {
11775        cx.focus_self(window);
11776        editor.move_to_end(&MoveToEnd, window, cx);
11777        editor.handle_input(".", window, cx);
11778    });
11779    cx.run_until_parked();
11780    completion_handle.next().await.unwrap();
11781
11782    editor.update(cx, |editor, _| {
11783        assert!(editor.context_menu_visible());
11784        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11785        {
11786            let completion_labels = menu
11787                .completions
11788                .borrow()
11789                .iter()
11790                .map(|c| c.label.text.clone())
11791                .collect::<Vec<_>>();
11792            assert_eq!(
11793                completion_labels,
11794                &[
11795                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
11796                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
11797                    "single line label 2 d e f ",
11798                    "a b c g h i ",
11799                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
11800                ],
11801                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
11802            );
11803
11804            for completion in menu
11805                .completions
11806                .borrow()
11807                .iter() {
11808                    assert_eq!(
11809                        completion.label.filter_range,
11810                        0..completion.label.text.len(),
11811                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
11812                    );
11813                }
11814        } else {
11815            panic!("expected completion menu to be open");
11816        }
11817    });
11818}
11819
11820#[gpui::test]
11821async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
11822    init_test(cx, |_| {});
11823    let mut cx = EditorLspTestContext::new_rust(
11824        lsp::ServerCapabilities {
11825            completion_provider: Some(lsp::CompletionOptions {
11826                trigger_characters: Some(vec![".".to_string()]),
11827                ..Default::default()
11828            }),
11829            ..Default::default()
11830        },
11831        cx,
11832    )
11833    .await;
11834    cx.lsp
11835        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11836            Ok(Some(lsp::CompletionResponse::Array(vec![
11837                lsp::CompletionItem {
11838                    label: "first".into(),
11839                    ..Default::default()
11840                },
11841                lsp::CompletionItem {
11842                    label: "last".into(),
11843                    ..Default::default()
11844                },
11845            ])))
11846        });
11847    cx.set_state("variableˇ");
11848    cx.simulate_keystroke(".");
11849    cx.executor().run_until_parked();
11850
11851    cx.update_editor(|editor, _, _| {
11852        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11853        {
11854            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
11855        } else {
11856            panic!("expected completion menu to be open");
11857        }
11858    });
11859
11860    cx.update_editor(|editor, window, cx| {
11861        editor.move_page_down(&MovePageDown::default(), window, cx);
11862        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11863        {
11864            assert!(
11865                menu.selected_item == 1,
11866                "expected PageDown to select the last item from the context menu"
11867            );
11868        } else {
11869            panic!("expected completion menu to stay open after PageDown");
11870        }
11871    });
11872
11873    cx.update_editor(|editor, window, cx| {
11874        editor.move_page_up(&MovePageUp::default(), window, cx);
11875        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11876        {
11877            assert!(
11878                menu.selected_item == 0,
11879                "expected PageUp to select the first item from the context menu"
11880            );
11881        } else {
11882            panic!("expected completion menu to stay open after PageUp");
11883        }
11884    });
11885}
11886
11887#[gpui::test]
11888async fn test_as_is_completions(cx: &mut TestAppContext) {
11889    init_test(cx, |_| {});
11890    let mut cx = EditorLspTestContext::new_rust(
11891        lsp::ServerCapabilities {
11892            completion_provider: Some(lsp::CompletionOptions {
11893                ..Default::default()
11894            }),
11895            ..Default::default()
11896        },
11897        cx,
11898    )
11899    .await;
11900    cx.lsp
11901        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11902            Ok(Some(lsp::CompletionResponse::Array(vec![
11903                lsp::CompletionItem {
11904                    label: "unsafe".into(),
11905                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11906                        range: lsp::Range {
11907                            start: lsp::Position {
11908                                line: 1,
11909                                character: 2,
11910                            },
11911                            end: lsp::Position {
11912                                line: 1,
11913                                character: 3,
11914                            },
11915                        },
11916                        new_text: "unsafe".to_string(),
11917                    })),
11918                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
11919                    ..Default::default()
11920                },
11921            ])))
11922        });
11923    cx.set_state("fn a() {}\n");
11924    cx.executor().run_until_parked();
11925    cx.update_editor(|editor, window, cx| {
11926        editor.show_completions(
11927            &ShowCompletions {
11928                trigger: Some("\n".into()),
11929            },
11930            window,
11931            cx,
11932        );
11933    });
11934    cx.executor().run_until_parked();
11935
11936    cx.update_editor(|editor, window, cx| {
11937        editor.confirm_completion(&Default::default(), window, cx)
11938    });
11939    cx.executor().run_until_parked();
11940    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
11941}
11942
11943#[gpui::test]
11944async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
11945    init_test(cx, |_| {});
11946
11947    let mut cx = EditorLspTestContext::new_rust(
11948        lsp::ServerCapabilities {
11949            completion_provider: Some(lsp::CompletionOptions {
11950                trigger_characters: Some(vec![".".to_string()]),
11951                resolve_provider: Some(true),
11952                ..Default::default()
11953            }),
11954            ..Default::default()
11955        },
11956        cx,
11957    )
11958    .await;
11959
11960    cx.set_state("fn main() { let a = 2ˇ; }");
11961    cx.simulate_keystroke(".");
11962    let completion_item = lsp::CompletionItem {
11963        label: "Some".into(),
11964        kind: Some(lsp::CompletionItemKind::SNIPPET),
11965        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11966        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11967            kind: lsp::MarkupKind::Markdown,
11968            value: "```rust\nSome(2)\n```".to_string(),
11969        })),
11970        deprecated: Some(false),
11971        sort_text: Some("Some".to_string()),
11972        filter_text: Some("Some".to_string()),
11973        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11974        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11975            range: lsp::Range {
11976                start: lsp::Position {
11977                    line: 0,
11978                    character: 22,
11979                },
11980                end: lsp::Position {
11981                    line: 0,
11982                    character: 22,
11983                },
11984            },
11985            new_text: "Some(2)".to_string(),
11986        })),
11987        additional_text_edits: Some(vec![lsp::TextEdit {
11988            range: lsp::Range {
11989                start: lsp::Position {
11990                    line: 0,
11991                    character: 20,
11992                },
11993                end: lsp::Position {
11994                    line: 0,
11995                    character: 22,
11996                },
11997            },
11998            new_text: "".to_string(),
11999        }]),
12000        ..Default::default()
12001    };
12002
12003    let closure_completion_item = completion_item.clone();
12004    let counter = Arc::new(AtomicUsize::new(0));
12005    let counter_clone = counter.clone();
12006    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12007        let task_completion_item = closure_completion_item.clone();
12008        counter_clone.fetch_add(1, atomic::Ordering::Release);
12009        async move {
12010            Ok(Some(lsp::CompletionResponse::Array(vec![
12011                task_completion_item,
12012            ])))
12013        }
12014    });
12015
12016    cx.condition(|editor, _| editor.context_menu_visible())
12017        .await;
12018    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12019    assert!(request.next().await.is_some());
12020    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12021
12022    cx.simulate_keystrokes("S o m");
12023    cx.condition(|editor, _| editor.context_menu_visible())
12024        .await;
12025    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12026    assert!(request.next().await.is_some());
12027    assert!(request.next().await.is_some());
12028    assert!(request.next().await.is_some());
12029    request.close();
12030    assert!(request.next().await.is_none());
12031    assert_eq!(
12032        counter.load(atomic::Ordering::Acquire),
12033        4,
12034        "With the completions menu open, only one LSP request should happen per input"
12035    );
12036}
12037
12038#[gpui::test]
12039async fn test_toggle_comment(cx: &mut TestAppContext) {
12040    init_test(cx, |_| {});
12041    let mut cx = EditorTestContext::new(cx).await;
12042    let language = Arc::new(Language::new(
12043        LanguageConfig {
12044            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12045            ..Default::default()
12046        },
12047        Some(tree_sitter_rust::LANGUAGE.into()),
12048    ));
12049    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12050
12051    // If multiple selections intersect a line, the line is only toggled once.
12052    cx.set_state(indoc! {"
12053        fn a() {
12054            «//b();
12055            ˇ»// «c();
12056            //ˇ»  d();
12057        }
12058    "});
12059
12060    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12061
12062    cx.assert_editor_state(indoc! {"
12063        fn a() {
12064            «b();
12065            c();
12066            ˇ» d();
12067        }
12068    "});
12069
12070    // The comment prefix is inserted at the same column for every line in a
12071    // selection.
12072    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12073
12074    cx.assert_editor_state(indoc! {"
12075        fn a() {
12076            // «b();
12077            // c();
12078            ˇ»//  d();
12079        }
12080    "});
12081
12082    // If a selection ends at the beginning of a line, that line is not toggled.
12083    cx.set_selections_state(indoc! {"
12084        fn a() {
12085            // b();
12086            «// c();
12087        ˇ»    //  d();
12088        }
12089    "});
12090
12091    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12092
12093    cx.assert_editor_state(indoc! {"
12094        fn a() {
12095            // b();
12096            «c();
12097        ˇ»    //  d();
12098        }
12099    "});
12100
12101    // If a selection span a single line and is empty, the line is toggled.
12102    cx.set_state(indoc! {"
12103        fn a() {
12104            a();
12105            b();
12106        ˇ
12107        }
12108    "});
12109
12110    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12111
12112    cx.assert_editor_state(indoc! {"
12113        fn a() {
12114            a();
12115            b();
12116        //•ˇ
12117        }
12118    "});
12119
12120    // If a selection span multiple lines, empty lines are not toggled.
12121    cx.set_state(indoc! {"
12122        fn a() {
12123            «a();
12124
12125            c();ˇ»
12126        }
12127    "});
12128
12129    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12130
12131    cx.assert_editor_state(indoc! {"
12132        fn a() {
12133            // «a();
12134
12135            // c();ˇ»
12136        }
12137    "});
12138
12139    // If a selection includes multiple comment prefixes, all lines are uncommented.
12140    cx.set_state(indoc! {"
12141        fn a() {
12142            «// a();
12143            /// b();
12144            //! c();ˇ»
12145        }
12146    "});
12147
12148    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12149
12150    cx.assert_editor_state(indoc! {"
12151        fn a() {
12152            «a();
12153            b();
12154            c();ˇ»
12155        }
12156    "});
12157}
12158
12159#[gpui::test]
12160async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12161    init_test(cx, |_| {});
12162    let mut cx = EditorTestContext::new(cx).await;
12163    let language = Arc::new(Language::new(
12164        LanguageConfig {
12165            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12166            ..Default::default()
12167        },
12168        Some(tree_sitter_rust::LANGUAGE.into()),
12169    ));
12170    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12171
12172    let toggle_comments = &ToggleComments {
12173        advance_downwards: false,
12174        ignore_indent: true,
12175    };
12176
12177    // If multiple selections intersect a line, the line is only toggled once.
12178    cx.set_state(indoc! {"
12179        fn a() {
12180        //    «b();
12181        //    c();
12182        //    ˇ» d();
12183        }
12184    "});
12185
12186    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12187
12188    cx.assert_editor_state(indoc! {"
12189        fn a() {
12190            «b();
12191            c();
12192            ˇ» d();
12193        }
12194    "});
12195
12196    // The comment prefix is inserted at the beginning of each line
12197    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12198
12199    cx.assert_editor_state(indoc! {"
12200        fn a() {
12201        //    «b();
12202        //    c();
12203        //    ˇ» d();
12204        }
12205    "});
12206
12207    // If a selection ends at the beginning of a line, that line is not toggled.
12208    cx.set_selections_state(indoc! {"
12209        fn a() {
12210        //    b();
12211        //    «c();
12212        ˇ»//     d();
12213        }
12214    "});
12215
12216    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12217
12218    cx.assert_editor_state(indoc! {"
12219        fn a() {
12220        //    b();
12221            «c();
12222        ˇ»//     d();
12223        }
12224    "});
12225
12226    // If a selection span a single line and is empty, the line is toggled.
12227    cx.set_state(indoc! {"
12228        fn a() {
12229            a();
12230            b();
12231        ˇ
12232        }
12233    "});
12234
12235    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12236
12237    cx.assert_editor_state(indoc! {"
12238        fn a() {
12239            a();
12240            b();
12241        //ˇ
12242        }
12243    "});
12244
12245    // If a selection span multiple lines, empty lines are not toggled.
12246    cx.set_state(indoc! {"
12247        fn a() {
12248            «a();
12249
12250            c();ˇ»
12251        }
12252    "});
12253
12254    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12255
12256    cx.assert_editor_state(indoc! {"
12257        fn a() {
12258        //    «a();
12259
12260        //    c();ˇ»
12261        }
12262    "});
12263
12264    // If a selection includes multiple comment prefixes, all lines are uncommented.
12265    cx.set_state(indoc! {"
12266        fn a() {
12267        //    «a();
12268        ///    b();
12269        //!    c();ˇ»
12270        }
12271    "});
12272
12273    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12274
12275    cx.assert_editor_state(indoc! {"
12276        fn a() {
12277            «a();
12278            b();
12279            c();ˇ»
12280        }
12281    "});
12282}
12283
12284#[gpui::test]
12285async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
12286    init_test(cx, |_| {});
12287
12288    let language = Arc::new(Language::new(
12289        LanguageConfig {
12290            line_comments: vec!["// ".into()],
12291            ..Default::default()
12292        },
12293        Some(tree_sitter_rust::LANGUAGE.into()),
12294    ));
12295
12296    let mut cx = EditorTestContext::new(cx).await;
12297
12298    cx.language_registry().add(language.clone());
12299    cx.update_buffer(|buffer, cx| {
12300        buffer.set_language(Some(language), cx);
12301    });
12302
12303    let toggle_comments = &ToggleComments {
12304        advance_downwards: true,
12305        ignore_indent: false,
12306    };
12307
12308    // Single cursor on one line -> advance
12309    // Cursor moves horizontally 3 characters as well on non-blank line
12310    cx.set_state(indoc!(
12311        "fn a() {
12312             ˇdog();
12313             cat();
12314        }"
12315    ));
12316    cx.update_editor(|editor, window, cx| {
12317        editor.toggle_comments(toggle_comments, window, cx);
12318    });
12319    cx.assert_editor_state(indoc!(
12320        "fn a() {
12321             // dog();
12322             catˇ();
12323        }"
12324    ));
12325
12326    // Single selection on one line -> don't advance
12327    cx.set_state(indoc!(
12328        "fn a() {
12329             «dog()ˇ»;
12330             cat();
12331        }"
12332    ));
12333    cx.update_editor(|editor, window, cx| {
12334        editor.toggle_comments(toggle_comments, window, cx);
12335    });
12336    cx.assert_editor_state(indoc!(
12337        "fn a() {
12338             // «dog()ˇ»;
12339             cat();
12340        }"
12341    ));
12342
12343    // Multiple cursors on one line -> advance
12344    cx.set_state(indoc!(
12345        "fn a() {
12346             ˇdˇog();
12347             cat();
12348        }"
12349    ));
12350    cx.update_editor(|editor, window, cx| {
12351        editor.toggle_comments(toggle_comments, window, cx);
12352    });
12353    cx.assert_editor_state(indoc!(
12354        "fn a() {
12355             // dog();
12356             catˇ(ˇ);
12357        }"
12358    ));
12359
12360    // Multiple cursors on one line, with selection -> don't advance
12361    cx.set_state(indoc!(
12362        "fn a() {
12363             ˇdˇog«()ˇ»;
12364             cat();
12365        }"
12366    ));
12367    cx.update_editor(|editor, window, cx| {
12368        editor.toggle_comments(toggle_comments, window, cx);
12369    });
12370    cx.assert_editor_state(indoc!(
12371        "fn a() {
12372             // ˇdˇog«()ˇ»;
12373             cat();
12374        }"
12375    ));
12376
12377    // Single cursor on one line -> advance
12378    // Cursor moves to column 0 on blank line
12379    cx.set_state(indoc!(
12380        "fn a() {
12381             ˇdog();
12382
12383             cat();
12384        }"
12385    ));
12386    cx.update_editor(|editor, window, cx| {
12387        editor.toggle_comments(toggle_comments, window, cx);
12388    });
12389    cx.assert_editor_state(indoc!(
12390        "fn a() {
12391             // dog();
12392        ˇ
12393             cat();
12394        }"
12395    ));
12396
12397    // Single cursor on one line -> advance
12398    // Cursor starts and ends at column 0
12399    cx.set_state(indoc!(
12400        "fn a() {
12401         ˇ    dog();
12402             cat();
12403        }"
12404    ));
12405    cx.update_editor(|editor, window, cx| {
12406        editor.toggle_comments(toggle_comments, window, cx);
12407    });
12408    cx.assert_editor_state(indoc!(
12409        "fn a() {
12410             // dog();
12411         ˇ    cat();
12412        }"
12413    ));
12414}
12415
12416#[gpui::test]
12417async fn test_toggle_block_comment(cx: &mut TestAppContext) {
12418    init_test(cx, |_| {});
12419
12420    let mut cx = EditorTestContext::new(cx).await;
12421
12422    let html_language = Arc::new(
12423        Language::new(
12424            LanguageConfig {
12425                name: "HTML".into(),
12426                block_comment: Some(("<!-- ".into(), " -->".into())),
12427                ..Default::default()
12428            },
12429            Some(tree_sitter_html::LANGUAGE.into()),
12430        )
12431        .with_injection_query(
12432            r#"
12433            (script_element
12434                (raw_text) @injection.content
12435                (#set! injection.language "javascript"))
12436            "#,
12437        )
12438        .unwrap(),
12439    );
12440
12441    let javascript_language = Arc::new(Language::new(
12442        LanguageConfig {
12443            name: "JavaScript".into(),
12444            line_comments: vec!["// ".into()],
12445            ..Default::default()
12446        },
12447        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12448    ));
12449
12450    cx.language_registry().add(html_language.clone());
12451    cx.language_registry().add(javascript_language.clone());
12452    cx.update_buffer(|buffer, cx| {
12453        buffer.set_language(Some(html_language), cx);
12454    });
12455
12456    // Toggle comments for empty selections
12457    cx.set_state(
12458        &r#"
12459            <p>A</p>ˇ
12460            <p>B</p>ˇ
12461            <p>C</p>ˇ
12462        "#
12463        .unindent(),
12464    );
12465    cx.update_editor(|editor, window, cx| {
12466        editor.toggle_comments(&ToggleComments::default(), window, cx)
12467    });
12468    cx.assert_editor_state(
12469        &r#"
12470            <!-- <p>A</p>ˇ -->
12471            <!-- <p>B</p>ˇ -->
12472            <!-- <p>C</p>ˇ -->
12473        "#
12474        .unindent(),
12475    );
12476    cx.update_editor(|editor, window, cx| {
12477        editor.toggle_comments(&ToggleComments::default(), window, cx)
12478    });
12479    cx.assert_editor_state(
12480        &r#"
12481            <p>A</p>ˇ
12482            <p>B</p>ˇ
12483            <p>C</p>ˇ
12484        "#
12485        .unindent(),
12486    );
12487
12488    // Toggle comments for mixture of empty and non-empty selections, where
12489    // multiple selections occupy a given line.
12490    cx.set_state(
12491        &r#"
12492            <p>A«</p>
12493            <p>ˇ»B</p>ˇ
12494            <p>C«</p>
12495            <p>ˇ»D</p>ˇ
12496        "#
12497        .unindent(),
12498    );
12499
12500    cx.update_editor(|editor, window, cx| {
12501        editor.toggle_comments(&ToggleComments::default(), window, cx)
12502    });
12503    cx.assert_editor_state(
12504        &r#"
12505            <!-- <p>A«</p>
12506            <p>ˇ»B</p>ˇ -->
12507            <!-- <p>C«</p>
12508            <p>ˇ»D</p>ˇ -->
12509        "#
12510        .unindent(),
12511    );
12512    cx.update_editor(|editor, window, cx| {
12513        editor.toggle_comments(&ToggleComments::default(), window, cx)
12514    });
12515    cx.assert_editor_state(
12516        &r#"
12517            <p>A«</p>
12518            <p>ˇ»B</p>ˇ
12519            <p>C«</p>
12520            <p>ˇ»D</p>ˇ
12521        "#
12522        .unindent(),
12523    );
12524
12525    // Toggle comments when different languages are active for different
12526    // selections.
12527    cx.set_state(
12528        &r#"
12529            ˇ<script>
12530                ˇvar x = new Y();
12531            ˇ</script>
12532        "#
12533        .unindent(),
12534    );
12535    cx.executor().run_until_parked();
12536    cx.update_editor(|editor, window, cx| {
12537        editor.toggle_comments(&ToggleComments::default(), window, cx)
12538    });
12539    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
12540    // Uncommenting and commenting from this position brings in even more wrong artifacts.
12541    cx.assert_editor_state(
12542        &r#"
12543            <!-- ˇ<script> -->
12544                // ˇvar x = new Y();
12545            <!-- ˇ</script> -->
12546        "#
12547        .unindent(),
12548    );
12549}
12550
12551#[gpui::test]
12552fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
12553    init_test(cx, |_| {});
12554
12555    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12556    let multibuffer = cx.new(|cx| {
12557        let mut multibuffer = MultiBuffer::new(ReadWrite);
12558        multibuffer.push_excerpts(
12559            buffer.clone(),
12560            [
12561                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
12562                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
12563            ],
12564            cx,
12565        );
12566        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
12567        multibuffer
12568    });
12569
12570    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12571    editor.update_in(cx, |editor, window, cx| {
12572        assert_eq!(editor.text(cx), "aaaa\nbbbb");
12573        editor.change_selections(None, window, cx, |s| {
12574            s.select_ranges([
12575                Point::new(0, 0)..Point::new(0, 0),
12576                Point::new(1, 0)..Point::new(1, 0),
12577            ])
12578        });
12579
12580        editor.handle_input("X", window, cx);
12581        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
12582        assert_eq!(
12583            editor.selections.ranges(cx),
12584            [
12585                Point::new(0, 1)..Point::new(0, 1),
12586                Point::new(1, 1)..Point::new(1, 1),
12587            ]
12588        );
12589
12590        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
12591        editor.change_selections(None, window, cx, |s| {
12592            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
12593        });
12594        editor.backspace(&Default::default(), window, cx);
12595        assert_eq!(editor.text(cx), "Xa\nbbb");
12596        assert_eq!(
12597            editor.selections.ranges(cx),
12598            [Point::new(1, 0)..Point::new(1, 0)]
12599        );
12600
12601        editor.change_selections(None, window, cx, |s| {
12602            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
12603        });
12604        editor.backspace(&Default::default(), window, cx);
12605        assert_eq!(editor.text(cx), "X\nbb");
12606        assert_eq!(
12607            editor.selections.ranges(cx),
12608            [Point::new(0, 1)..Point::new(0, 1)]
12609        );
12610    });
12611}
12612
12613#[gpui::test]
12614fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
12615    init_test(cx, |_| {});
12616
12617    let markers = vec![('[', ']').into(), ('(', ')').into()];
12618    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
12619        indoc! {"
12620            [aaaa
12621            (bbbb]
12622            cccc)",
12623        },
12624        markers.clone(),
12625    );
12626    let excerpt_ranges = markers.into_iter().map(|marker| {
12627        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
12628        ExcerptRange::new(context.clone())
12629    });
12630    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
12631    let multibuffer = cx.new(|cx| {
12632        let mut multibuffer = MultiBuffer::new(ReadWrite);
12633        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
12634        multibuffer
12635    });
12636
12637    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12638    editor.update_in(cx, |editor, window, cx| {
12639        let (expected_text, selection_ranges) = marked_text_ranges(
12640            indoc! {"
12641                aaaa
12642                bˇbbb
12643                bˇbbˇb
12644                cccc"
12645            },
12646            true,
12647        );
12648        assert_eq!(editor.text(cx), expected_text);
12649        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
12650
12651        editor.handle_input("X", window, cx);
12652
12653        let (expected_text, expected_selections) = marked_text_ranges(
12654            indoc! {"
12655                aaaa
12656                bXˇbbXb
12657                bXˇbbXˇb
12658                cccc"
12659            },
12660            false,
12661        );
12662        assert_eq!(editor.text(cx), expected_text);
12663        assert_eq!(editor.selections.ranges(cx), expected_selections);
12664
12665        editor.newline(&Newline, window, cx);
12666        let (expected_text, expected_selections) = marked_text_ranges(
12667            indoc! {"
12668                aaaa
12669                bX
12670                ˇbbX
12671                b
12672                bX
12673                ˇbbX
12674                ˇb
12675                cccc"
12676            },
12677            false,
12678        );
12679        assert_eq!(editor.text(cx), expected_text);
12680        assert_eq!(editor.selections.ranges(cx), expected_selections);
12681    });
12682}
12683
12684#[gpui::test]
12685fn test_refresh_selections(cx: &mut TestAppContext) {
12686    init_test(cx, |_| {});
12687
12688    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12689    let mut excerpt1_id = None;
12690    let multibuffer = cx.new(|cx| {
12691        let mut multibuffer = MultiBuffer::new(ReadWrite);
12692        excerpt1_id = multibuffer
12693            .push_excerpts(
12694                buffer.clone(),
12695                [
12696                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12697                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12698                ],
12699                cx,
12700            )
12701            .into_iter()
12702            .next();
12703        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12704        multibuffer
12705    });
12706
12707    let editor = cx.add_window(|window, cx| {
12708        let mut editor = build_editor(multibuffer.clone(), window, cx);
12709        let snapshot = editor.snapshot(window, cx);
12710        editor.change_selections(None, window, cx, |s| {
12711            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
12712        });
12713        editor.begin_selection(
12714            Point::new(2, 1).to_display_point(&snapshot),
12715            true,
12716            1,
12717            window,
12718            cx,
12719        );
12720        assert_eq!(
12721            editor.selections.ranges(cx),
12722            [
12723                Point::new(1, 3)..Point::new(1, 3),
12724                Point::new(2, 1)..Point::new(2, 1),
12725            ]
12726        );
12727        editor
12728    });
12729
12730    // Refreshing selections is a no-op when excerpts haven't changed.
12731    _ = editor.update(cx, |editor, window, cx| {
12732        editor.change_selections(None, window, cx, |s| s.refresh());
12733        assert_eq!(
12734            editor.selections.ranges(cx),
12735            [
12736                Point::new(1, 3)..Point::new(1, 3),
12737                Point::new(2, 1)..Point::new(2, 1),
12738            ]
12739        );
12740    });
12741
12742    multibuffer.update(cx, |multibuffer, cx| {
12743        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12744    });
12745    _ = editor.update(cx, |editor, window, cx| {
12746        // Removing an excerpt causes the first selection to become degenerate.
12747        assert_eq!(
12748            editor.selections.ranges(cx),
12749            [
12750                Point::new(0, 0)..Point::new(0, 0),
12751                Point::new(0, 1)..Point::new(0, 1)
12752            ]
12753        );
12754
12755        // Refreshing selections will relocate the first selection to the original buffer
12756        // location.
12757        editor.change_selections(None, window, cx, |s| s.refresh());
12758        assert_eq!(
12759            editor.selections.ranges(cx),
12760            [
12761                Point::new(0, 1)..Point::new(0, 1),
12762                Point::new(0, 3)..Point::new(0, 3)
12763            ]
12764        );
12765        assert!(editor.selections.pending_anchor().is_some());
12766    });
12767}
12768
12769#[gpui::test]
12770fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
12771    init_test(cx, |_| {});
12772
12773    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
12774    let mut excerpt1_id = None;
12775    let multibuffer = cx.new(|cx| {
12776        let mut multibuffer = MultiBuffer::new(ReadWrite);
12777        excerpt1_id = multibuffer
12778            .push_excerpts(
12779                buffer.clone(),
12780                [
12781                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
12782                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
12783                ],
12784                cx,
12785            )
12786            .into_iter()
12787            .next();
12788        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
12789        multibuffer
12790    });
12791
12792    let editor = cx.add_window(|window, cx| {
12793        let mut editor = build_editor(multibuffer.clone(), window, cx);
12794        let snapshot = editor.snapshot(window, cx);
12795        editor.begin_selection(
12796            Point::new(1, 3).to_display_point(&snapshot),
12797            false,
12798            1,
12799            window,
12800            cx,
12801        );
12802        assert_eq!(
12803            editor.selections.ranges(cx),
12804            [Point::new(1, 3)..Point::new(1, 3)]
12805        );
12806        editor
12807    });
12808
12809    multibuffer.update(cx, |multibuffer, cx| {
12810        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
12811    });
12812    _ = editor.update(cx, |editor, window, cx| {
12813        assert_eq!(
12814            editor.selections.ranges(cx),
12815            [Point::new(0, 0)..Point::new(0, 0)]
12816        );
12817
12818        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
12819        editor.change_selections(None, window, cx, |s| s.refresh());
12820        assert_eq!(
12821            editor.selections.ranges(cx),
12822            [Point::new(0, 3)..Point::new(0, 3)]
12823        );
12824        assert!(editor.selections.pending_anchor().is_some());
12825    });
12826}
12827
12828#[gpui::test]
12829async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
12830    init_test(cx, |_| {});
12831
12832    let language = Arc::new(
12833        Language::new(
12834            LanguageConfig {
12835                brackets: BracketPairConfig {
12836                    pairs: vec![
12837                        BracketPair {
12838                            start: "{".to_string(),
12839                            end: "}".to_string(),
12840                            close: true,
12841                            surround: true,
12842                            newline: true,
12843                        },
12844                        BracketPair {
12845                            start: "/* ".to_string(),
12846                            end: " */".to_string(),
12847                            close: true,
12848                            surround: true,
12849                            newline: true,
12850                        },
12851                    ],
12852                    ..Default::default()
12853                },
12854                ..Default::default()
12855            },
12856            Some(tree_sitter_rust::LANGUAGE.into()),
12857        )
12858        .with_indents_query("")
12859        .unwrap(),
12860    );
12861
12862    let text = concat!(
12863        "{   }\n",     //
12864        "  x\n",       //
12865        "  /*   */\n", //
12866        "x\n",         //
12867        "{{} }\n",     //
12868    );
12869
12870    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12871    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12872    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12873    editor
12874        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12875        .await;
12876
12877    editor.update_in(cx, |editor, window, cx| {
12878        editor.change_selections(None, window, cx, |s| {
12879            s.select_display_ranges([
12880                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
12881                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
12882                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
12883            ])
12884        });
12885        editor.newline(&Newline, window, cx);
12886
12887        assert_eq!(
12888            editor.buffer().read(cx).read(cx).text(),
12889            concat!(
12890                "{ \n",    // Suppress rustfmt
12891                "\n",      //
12892                "}\n",     //
12893                "  x\n",   //
12894                "  /* \n", //
12895                "  \n",    //
12896                "  */\n",  //
12897                "x\n",     //
12898                "{{} \n",  //
12899                "}\n",     //
12900            )
12901        );
12902    });
12903}
12904
12905#[gpui::test]
12906fn test_highlighted_ranges(cx: &mut TestAppContext) {
12907    init_test(cx, |_| {});
12908
12909    let editor = cx.add_window(|window, cx| {
12910        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
12911        build_editor(buffer.clone(), window, cx)
12912    });
12913
12914    _ = editor.update(cx, |editor, window, cx| {
12915        struct Type1;
12916        struct Type2;
12917
12918        let buffer = editor.buffer.read(cx).snapshot(cx);
12919
12920        let anchor_range =
12921            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
12922
12923        editor.highlight_background::<Type1>(
12924            &[
12925                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
12926                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
12927                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
12928                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
12929            ],
12930            |_| Hsla::red(),
12931            cx,
12932        );
12933        editor.highlight_background::<Type2>(
12934            &[
12935                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
12936                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
12937                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
12938                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
12939            ],
12940            |_| Hsla::green(),
12941            cx,
12942        );
12943
12944        let snapshot = editor.snapshot(window, cx);
12945        let mut highlighted_ranges = editor.background_highlights_in_range(
12946            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
12947            &snapshot,
12948            cx.theme().colors(),
12949        );
12950        // Enforce a consistent ordering based on color without relying on the ordering of the
12951        // highlight's `TypeId` which is non-executor.
12952        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
12953        assert_eq!(
12954            highlighted_ranges,
12955            &[
12956                (
12957                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
12958                    Hsla::red(),
12959                ),
12960                (
12961                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12962                    Hsla::red(),
12963                ),
12964                (
12965                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
12966                    Hsla::green(),
12967                ),
12968                (
12969                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
12970                    Hsla::green(),
12971                ),
12972            ]
12973        );
12974        assert_eq!(
12975            editor.background_highlights_in_range(
12976                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
12977                &snapshot,
12978                cx.theme().colors(),
12979            ),
12980            &[(
12981                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
12982                Hsla::red(),
12983            )]
12984        );
12985    });
12986}
12987
12988#[gpui::test]
12989async fn test_following(cx: &mut TestAppContext) {
12990    init_test(cx, |_| {});
12991
12992    let fs = FakeFs::new(cx.executor());
12993    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
12994
12995    let buffer = project.update(cx, |project, cx| {
12996        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
12997        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
12998    });
12999    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13000    let follower = cx.update(|cx| {
13001        cx.open_window(
13002            WindowOptions {
13003                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13004                    gpui::Point::new(px(0.), px(0.)),
13005                    gpui::Point::new(px(10.), px(80.)),
13006                ))),
13007                ..Default::default()
13008            },
13009            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13010        )
13011        .unwrap()
13012    });
13013
13014    let is_still_following = Rc::new(RefCell::new(true));
13015    let follower_edit_event_count = Rc::new(RefCell::new(0));
13016    let pending_update = Rc::new(RefCell::new(None));
13017    let leader_entity = leader.root(cx).unwrap();
13018    let follower_entity = follower.root(cx).unwrap();
13019    _ = follower.update(cx, {
13020        let update = pending_update.clone();
13021        let is_still_following = is_still_following.clone();
13022        let follower_edit_event_count = follower_edit_event_count.clone();
13023        |_, window, cx| {
13024            cx.subscribe_in(
13025                &leader_entity,
13026                window,
13027                move |_, leader, event, window, cx| {
13028                    leader.read(cx).add_event_to_update_proto(
13029                        event,
13030                        &mut update.borrow_mut(),
13031                        window,
13032                        cx,
13033                    );
13034                },
13035            )
13036            .detach();
13037
13038            cx.subscribe_in(
13039                &follower_entity,
13040                window,
13041                move |_, _, event: &EditorEvent, _window, _cx| {
13042                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13043                        *is_still_following.borrow_mut() = false;
13044                    }
13045
13046                    if let EditorEvent::BufferEdited = event {
13047                        *follower_edit_event_count.borrow_mut() += 1;
13048                    }
13049                },
13050            )
13051            .detach();
13052        }
13053    });
13054
13055    // Update the selections only
13056    _ = leader.update(cx, |leader, window, cx| {
13057        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13058    });
13059    follower
13060        .update(cx, |follower, window, cx| {
13061            follower.apply_update_proto(
13062                &project,
13063                pending_update.borrow_mut().take().unwrap(),
13064                window,
13065                cx,
13066            )
13067        })
13068        .unwrap()
13069        .await
13070        .unwrap();
13071    _ = follower.update(cx, |follower, _, cx| {
13072        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13073    });
13074    assert!(*is_still_following.borrow());
13075    assert_eq!(*follower_edit_event_count.borrow(), 0);
13076
13077    // Update the scroll position only
13078    _ = leader.update(cx, |leader, window, cx| {
13079        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13080    });
13081    follower
13082        .update(cx, |follower, window, cx| {
13083            follower.apply_update_proto(
13084                &project,
13085                pending_update.borrow_mut().take().unwrap(),
13086                window,
13087                cx,
13088            )
13089        })
13090        .unwrap()
13091        .await
13092        .unwrap();
13093    assert_eq!(
13094        follower
13095            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13096            .unwrap(),
13097        gpui::Point::new(1.5, 3.5)
13098    );
13099    assert!(*is_still_following.borrow());
13100    assert_eq!(*follower_edit_event_count.borrow(), 0);
13101
13102    // Update the selections and scroll position. The follower's scroll position is updated
13103    // via autoscroll, not via the leader's exact scroll position.
13104    _ = leader.update(cx, |leader, window, cx| {
13105        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13106        leader.request_autoscroll(Autoscroll::newest(), cx);
13107        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13108    });
13109    follower
13110        .update(cx, |follower, window, cx| {
13111            follower.apply_update_proto(
13112                &project,
13113                pending_update.borrow_mut().take().unwrap(),
13114                window,
13115                cx,
13116            )
13117        })
13118        .unwrap()
13119        .await
13120        .unwrap();
13121    _ = follower.update(cx, |follower, _, cx| {
13122        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13123        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13124    });
13125    assert!(*is_still_following.borrow());
13126
13127    // Creating a pending selection that precedes another selection
13128    _ = leader.update(cx, |leader, window, cx| {
13129        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13130        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13131    });
13132    follower
13133        .update(cx, |follower, window, cx| {
13134            follower.apply_update_proto(
13135                &project,
13136                pending_update.borrow_mut().take().unwrap(),
13137                window,
13138                cx,
13139            )
13140        })
13141        .unwrap()
13142        .await
13143        .unwrap();
13144    _ = follower.update(cx, |follower, _, cx| {
13145        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13146    });
13147    assert!(*is_still_following.borrow());
13148
13149    // Extend the pending selection so that it surrounds another selection
13150    _ = leader.update(cx, |leader, window, cx| {
13151        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13152    });
13153    follower
13154        .update(cx, |follower, window, cx| {
13155            follower.apply_update_proto(
13156                &project,
13157                pending_update.borrow_mut().take().unwrap(),
13158                window,
13159                cx,
13160            )
13161        })
13162        .unwrap()
13163        .await
13164        .unwrap();
13165    _ = follower.update(cx, |follower, _, cx| {
13166        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13167    });
13168
13169    // Scrolling locally breaks the follow
13170    _ = follower.update(cx, |follower, window, cx| {
13171        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13172        follower.set_scroll_anchor(
13173            ScrollAnchor {
13174                anchor: top_anchor,
13175                offset: gpui::Point::new(0.0, 0.5),
13176            },
13177            window,
13178            cx,
13179        );
13180    });
13181    assert!(!(*is_still_following.borrow()));
13182}
13183
13184#[gpui::test]
13185async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13186    init_test(cx, |_| {});
13187
13188    let fs = FakeFs::new(cx.executor());
13189    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13190    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13191    let pane = workspace
13192        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13193        .unwrap();
13194
13195    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13196
13197    let leader = pane.update_in(cx, |_, window, cx| {
13198        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13199        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13200    });
13201
13202    // Start following the editor when it has no excerpts.
13203    let mut state_message =
13204        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13205    let workspace_entity = workspace.root(cx).unwrap();
13206    let follower_1 = cx
13207        .update_window(*workspace.deref(), |_, window, cx| {
13208            Editor::from_state_proto(
13209                workspace_entity,
13210                ViewId {
13211                    creator: CollaboratorId::PeerId(PeerId::default()),
13212                    id: 0,
13213                },
13214                &mut state_message,
13215                window,
13216                cx,
13217            )
13218        })
13219        .unwrap()
13220        .unwrap()
13221        .await
13222        .unwrap();
13223
13224    let update_message = Rc::new(RefCell::new(None));
13225    follower_1.update_in(cx, {
13226        let update = update_message.clone();
13227        |_, window, cx| {
13228            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13229                leader.read(cx).add_event_to_update_proto(
13230                    event,
13231                    &mut update.borrow_mut(),
13232                    window,
13233                    cx,
13234                );
13235            })
13236            .detach();
13237        }
13238    });
13239
13240    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13241        (
13242            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13243            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13244        )
13245    });
13246
13247    // Insert some excerpts.
13248    leader.update(cx, |leader, cx| {
13249        leader.buffer.update(cx, |multibuffer, cx| {
13250            multibuffer.set_excerpts_for_path(
13251                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
13252                buffer_1.clone(),
13253                vec![
13254                    Point::row_range(0..3),
13255                    Point::row_range(1..6),
13256                    Point::row_range(12..15),
13257                ],
13258                0,
13259                cx,
13260            );
13261            multibuffer.set_excerpts_for_path(
13262                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
13263                buffer_2.clone(),
13264                vec![Point::row_range(0..6), Point::row_range(8..12)],
13265                0,
13266                cx,
13267            );
13268        });
13269    });
13270
13271    // Apply the update of adding the excerpts.
13272    follower_1
13273        .update_in(cx, |follower, window, cx| {
13274            follower.apply_update_proto(
13275                &project,
13276                update_message.borrow().clone().unwrap(),
13277                window,
13278                cx,
13279            )
13280        })
13281        .await
13282        .unwrap();
13283    assert_eq!(
13284        follower_1.update(cx, |editor, cx| editor.text(cx)),
13285        leader.update(cx, |editor, cx| editor.text(cx))
13286    );
13287    update_message.borrow_mut().take();
13288
13289    // Start following separately after it already has excerpts.
13290    let mut state_message =
13291        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13292    let workspace_entity = workspace.root(cx).unwrap();
13293    let follower_2 = cx
13294        .update_window(*workspace.deref(), |_, window, cx| {
13295            Editor::from_state_proto(
13296                workspace_entity,
13297                ViewId {
13298                    creator: CollaboratorId::PeerId(PeerId::default()),
13299                    id: 0,
13300                },
13301                &mut state_message,
13302                window,
13303                cx,
13304            )
13305        })
13306        .unwrap()
13307        .unwrap()
13308        .await
13309        .unwrap();
13310    assert_eq!(
13311        follower_2.update(cx, |editor, cx| editor.text(cx)),
13312        leader.update(cx, |editor, cx| editor.text(cx))
13313    );
13314
13315    // Remove some excerpts.
13316    leader.update(cx, |leader, cx| {
13317        leader.buffer.update(cx, |multibuffer, cx| {
13318            let excerpt_ids = multibuffer.excerpt_ids();
13319            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
13320            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
13321        });
13322    });
13323
13324    // Apply the update of removing the excerpts.
13325    follower_1
13326        .update_in(cx, |follower, window, cx| {
13327            follower.apply_update_proto(
13328                &project,
13329                update_message.borrow().clone().unwrap(),
13330                window,
13331                cx,
13332            )
13333        })
13334        .await
13335        .unwrap();
13336    follower_2
13337        .update_in(cx, |follower, window, cx| {
13338            follower.apply_update_proto(
13339                &project,
13340                update_message.borrow().clone().unwrap(),
13341                window,
13342                cx,
13343            )
13344        })
13345        .await
13346        .unwrap();
13347    update_message.borrow_mut().take();
13348    assert_eq!(
13349        follower_1.update(cx, |editor, cx| editor.text(cx)),
13350        leader.update(cx, |editor, cx| editor.text(cx))
13351    );
13352}
13353
13354#[gpui::test]
13355async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13356    init_test(cx, |_| {});
13357
13358    let mut cx = EditorTestContext::new(cx).await;
13359    let lsp_store =
13360        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
13361
13362    cx.set_state(indoc! {"
13363        ˇfn func(abc def: i32) -> u32 {
13364        }
13365    "});
13366
13367    cx.update(|_, cx| {
13368        lsp_store.update(cx, |lsp_store, cx| {
13369            lsp_store
13370                .update_diagnostics(
13371                    LanguageServerId(0),
13372                    lsp::PublishDiagnosticsParams {
13373                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
13374                        version: None,
13375                        diagnostics: vec![
13376                            lsp::Diagnostic {
13377                                range: lsp::Range::new(
13378                                    lsp::Position::new(0, 11),
13379                                    lsp::Position::new(0, 12),
13380                                ),
13381                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13382                                ..Default::default()
13383                            },
13384                            lsp::Diagnostic {
13385                                range: lsp::Range::new(
13386                                    lsp::Position::new(0, 12),
13387                                    lsp::Position::new(0, 15),
13388                                ),
13389                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13390                                ..Default::default()
13391                            },
13392                            lsp::Diagnostic {
13393                                range: lsp::Range::new(
13394                                    lsp::Position::new(0, 25),
13395                                    lsp::Position::new(0, 28),
13396                                ),
13397                                severity: Some(lsp::DiagnosticSeverity::ERROR),
13398                                ..Default::default()
13399                            },
13400                        ],
13401                    },
13402                    &[],
13403                    cx,
13404                )
13405                .unwrap()
13406        });
13407    });
13408
13409    executor.run_until_parked();
13410
13411    cx.update_editor(|editor, window, cx| {
13412        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13413    });
13414
13415    cx.assert_editor_state(indoc! {"
13416        fn func(abc def: i32) -> ˇu32 {
13417        }
13418    "});
13419
13420    cx.update_editor(|editor, window, cx| {
13421        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13422    });
13423
13424    cx.assert_editor_state(indoc! {"
13425        fn func(abc ˇdef: i32) -> u32 {
13426        }
13427    "});
13428
13429    cx.update_editor(|editor, window, cx| {
13430        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13431    });
13432
13433    cx.assert_editor_state(indoc! {"
13434        fn func(abcˇ def: i32) -> u32 {
13435        }
13436    "});
13437
13438    cx.update_editor(|editor, window, cx| {
13439        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
13440    });
13441
13442    cx.assert_editor_state(indoc! {"
13443        fn func(abc def: i32) -> ˇu32 {
13444        }
13445    "});
13446}
13447
13448#[gpui::test]
13449async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13450    init_test(cx, |_| {});
13451
13452    let mut cx = EditorTestContext::new(cx).await;
13453
13454    let diff_base = r#"
13455        use some::mod;
13456
13457        const A: u32 = 42;
13458
13459        fn main() {
13460            println!("hello");
13461
13462            println!("world");
13463        }
13464        "#
13465    .unindent();
13466
13467    // Edits are modified, removed, modified, added
13468    cx.set_state(
13469        &r#"
13470        use some::modified;
13471
13472        ˇ
13473        fn main() {
13474            println!("hello there");
13475
13476            println!("around the");
13477            println!("world");
13478        }
13479        "#
13480        .unindent(),
13481    );
13482
13483    cx.set_head_text(&diff_base);
13484    executor.run_until_parked();
13485
13486    cx.update_editor(|editor, window, cx| {
13487        //Wrap around the bottom of the buffer
13488        for _ in 0..3 {
13489            editor.go_to_next_hunk(&GoToHunk, window, cx);
13490        }
13491    });
13492
13493    cx.assert_editor_state(
13494        &r#"
13495        ˇuse some::modified;
13496
13497
13498        fn main() {
13499            println!("hello there");
13500
13501            println!("around the");
13502            println!("world");
13503        }
13504        "#
13505        .unindent(),
13506    );
13507
13508    cx.update_editor(|editor, window, cx| {
13509        //Wrap around the top of the buffer
13510        for _ in 0..2 {
13511            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13512        }
13513    });
13514
13515    cx.assert_editor_state(
13516        &r#"
13517        use some::modified;
13518
13519
13520        fn main() {
13521        ˇ    println!("hello there");
13522
13523            println!("around the");
13524            println!("world");
13525        }
13526        "#
13527        .unindent(),
13528    );
13529
13530    cx.update_editor(|editor, window, cx| {
13531        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13532    });
13533
13534    cx.assert_editor_state(
13535        &r#"
13536        use some::modified;
13537
13538        ˇ
13539        fn main() {
13540            println!("hello there");
13541
13542            println!("around the");
13543            println!("world");
13544        }
13545        "#
13546        .unindent(),
13547    );
13548
13549    cx.update_editor(|editor, window, cx| {
13550        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13551    });
13552
13553    cx.assert_editor_state(
13554        &r#"
13555        ˇuse some::modified;
13556
13557
13558        fn main() {
13559            println!("hello there");
13560
13561            println!("around the");
13562            println!("world");
13563        }
13564        "#
13565        .unindent(),
13566    );
13567
13568    cx.update_editor(|editor, window, cx| {
13569        for _ in 0..2 {
13570            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
13571        }
13572    });
13573
13574    cx.assert_editor_state(
13575        &r#"
13576        use some::modified;
13577
13578
13579        fn main() {
13580        ˇ    println!("hello there");
13581
13582            println!("around the");
13583            println!("world");
13584        }
13585        "#
13586        .unindent(),
13587    );
13588
13589    cx.update_editor(|editor, window, cx| {
13590        editor.fold(&Fold, window, cx);
13591    });
13592
13593    cx.update_editor(|editor, window, cx| {
13594        editor.go_to_next_hunk(&GoToHunk, window, cx);
13595    });
13596
13597    cx.assert_editor_state(
13598        &r#"
13599        ˇuse some::modified;
13600
13601
13602        fn main() {
13603            println!("hello there");
13604
13605            println!("around the");
13606            println!("world");
13607        }
13608        "#
13609        .unindent(),
13610    );
13611}
13612
13613#[test]
13614fn test_split_words() {
13615    fn split(text: &str) -> Vec<&str> {
13616        split_words(text).collect()
13617    }
13618
13619    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
13620    assert_eq!(split("hello_world"), &["hello_", "world"]);
13621    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
13622    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
13623    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
13624    assert_eq!(split("helloworld"), &["helloworld"]);
13625
13626    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
13627}
13628
13629#[gpui::test]
13630async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
13631    init_test(cx, |_| {});
13632
13633    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
13634    let mut assert = |before, after| {
13635        let _state_context = cx.set_state(before);
13636        cx.run_until_parked();
13637        cx.update_editor(|editor, window, cx| {
13638            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
13639        });
13640        cx.run_until_parked();
13641        cx.assert_editor_state(after);
13642    };
13643
13644    // Outside bracket jumps to outside of matching bracket
13645    assert("console.logˇ(var);", "console.log(var)ˇ;");
13646    assert("console.log(var)ˇ;", "console.logˇ(var);");
13647
13648    // Inside bracket jumps to inside of matching bracket
13649    assert("console.log(ˇvar);", "console.log(varˇ);");
13650    assert("console.log(varˇ);", "console.log(ˇvar);");
13651
13652    // When outside a bracket and inside, favor jumping to the inside bracket
13653    assert(
13654        "console.log('foo', [1, 2, 3]ˇ);",
13655        "console.log(ˇ'foo', [1, 2, 3]);",
13656    );
13657    assert(
13658        "console.log(ˇ'foo', [1, 2, 3]);",
13659        "console.log('foo', [1, 2, 3]ˇ);",
13660    );
13661
13662    // Bias forward if two options are equally likely
13663    assert(
13664        "let result = curried_fun()ˇ();",
13665        "let result = curried_fun()()ˇ;",
13666    );
13667
13668    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
13669    assert(
13670        indoc! {"
13671            function test() {
13672                console.log('test')ˇ
13673            }"},
13674        indoc! {"
13675            function test() {
13676                console.logˇ('test')
13677            }"},
13678    );
13679}
13680
13681#[gpui::test]
13682async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
13683    init_test(cx, |_| {});
13684
13685    let fs = FakeFs::new(cx.executor());
13686    fs.insert_tree(
13687        path!("/a"),
13688        json!({
13689            "main.rs": "fn main() { let a = 5; }",
13690            "other.rs": "// Test file",
13691        }),
13692    )
13693    .await;
13694    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13695
13696    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13697    language_registry.add(Arc::new(Language::new(
13698        LanguageConfig {
13699            name: "Rust".into(),
13700            matcher: LanguageMatcher {
13701                path_suffixes: vec!["rs".to_string()],
13702                ..Default::default()
13703            },
13704            brackets: BracketPairConfig {
13705                pairs: vec![BracketPair {
13706                    start: "{".to_string(),
13707                    end: "}".to_string(),
13708                    close: true,
13709                    surround: true,
13710                    newline: true,
13711                }],
13712                disabled_scopes_by_bracket_ix: Vec::new(),
13713            },
13714            ..Default::default()
13715        },
13716        Some(tree_sitter_rust::LANGUAGE.into()),
13717    )));
13718    let mut fake_servers = language_registry.register_fake_lsp(
13719        "Rust",
13720        FakeLspAdapter {
13721            capabilities: lsp::ServerCapabilities {
13722                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
13723                    first_trigger_character: "{".to_string(),
13724                    more_trigger_character: None,
13725                }),
13726                ..Default::default()
13727            },
13728            ..Default::default()
13729        },
13730    );
13731
13732    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13733
13734    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13735
13736    let worktree_id = workspace
13737        .update(cx, |workspace, _, cx| {
13738            workspace.project().update(cx, |project, cx| {
13739                project.worktrees(cx).next().unwrap().read(cx).id()
13740            })
13741        })
13742        .unwrap();
13743
13744    let buffer = project
13745        .update(cx, |project, cx| {
13746            project.open_local_buffer(path!("/a/main.rs"), cx)
13747        })
13748        .await
13749        .unwrap();
13750    let editor_handle = workspace
13751        .update(cx, |workspace, window, cx| {
13752            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
13753        })
13754        .unwrap()
13755        .await
13756        .unwrap()
13757        .downcast::<Editor>()
13758        .unwrap();
13759
13760    cx.executor().start_waiting();
13761    let fake_server = fake_servers.next().await.unwrap();
13762
13763    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
13764        |params, _| async move {
13765            assert_eq!(
13766                params.text_document_position.text_document.uri,
13767                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
13768            );
13769            assert_eq!(
13770                params.text_document_position.position,
13771                lsp::Position::new(0, 21),
13772            );
13773
13774            Ok(Some(vec![lsp::TextEdit {
13775                new_text: "]".to_string(),
13776                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
13777            }]))
13778        },
13779    );
13780
13781    editor_handle.update_in(cx, |editor, window, cx| {
13782        window.focus(&editor.focus_handle(cx));
13783        editor.change_selections(None, window, cx, |s| {
13784            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
13785        });
13786        editor.handle_input("{", window, cx);
13787    });
13788
13789    cx.executor().run_until_parked();
13790
13791    buffer.update(cx, |buffer, _| {
13792        assert_eq!(
13793            buffer.text(),
13794            "fn main() { let a = {5}; }",
13795            "No extra braces from on type formatting should appear in the buffer"
13796        )
13797    });
13798}
13799
13800#[gpui::test]
13801async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
13802    init_test(cx, |_| {});
13803
13804    let fs = FakeFs::new(cx.executor());
13805    fs.insert_tree(
13806        path!("/a"),
13807        json!({
13808            "main.rs": "fn main() { let a = 5; }",
13809            "other.rs": "// Test file",
13810        }),
13811    )
13812    .await;
13813
13814    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13815
13816    let server_restarts = Arc::new(AtomicUsize::new(0));
13817    let closure_restarts = Arc::clone(&server_restarts);
13818    let language_server_name = "test language server";
13819    let language_name: LanguageName = "Rust".into();
13820
13821    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13822    language_registry.add(Arc::new(Language::new(
13823        LanguageConfig {
13824            name: language_name.clone(),
13825            matcher: LanguageMatcher {
13826                path_suffixes: vec!["rs".to_string()],
13827                ..Default::default()
13828            },
13829            ..Default::default()
13830        },
13831        Some(tree_sitter_rust::LANGUAGE.into()),
13832    )));
13833    let mut fake_servers = language_registry.register_fake_lsp(
13834        "Rust",
13835        FakeLspAdapter {
13836            name: language_server_name,
13837            initialization_options: Some(json!({
13838                "testOptionValue": true
13839            })),
13840            initializer: Some(Box::new(move |fake_server| {
13841                let task_restarts = Arc::clone(&closure_restarts);
13842                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
13843                    task_restarts.fetch_add(1, atomic::Ordering::Release);
13844                    futures::future::ready(Ok(()))
13845                });
13846            })),
13847            ..Default::default()
13848        },
13849    );
13850
13851    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13852    let _buffer = project
13853        .update(cx, |project, cx| {
13854            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
13855        })
13856        .await
13857        .unwrap();
13858    let _fake_server = fake_servers.next().await.unwrap();
13859    update_test_language_settings(cx, |language_settings| {
13860        language_settings.languages.insert(
13861            language_name.clone(),
13862            LanguageSettingsContent {
13863                tab_size: NonZeroU32::new(8),
13864                ..Default::default()
13865            },
13866        );
13867    });
13868    cx.executor().run_until_parked();
13869    assert_eq!(
13870        server_restarts.load(atomic::Ordering::Acquire),
13871        0,
13872        "Should not restart LSP server on an unrelated change"
13873    );
13874
13875    update_test_project_settings(cx, |project_settings| {
13876        project_settings.lsp.insert(
13877            "Some other server name".into(),
13878            LspSettings {
13879                binary: None,
13880                settings: None,
13881                initialization_options: Some(json!({
13882                    "some other init value": false
13883                })),
13884                enable_lsp_tasks: false,
13885            },
13886        );
13887    });
13888    cx.executor().run_until_parked();
13889    assert_eq!(
13890        server_restarts.load(atomic::Ordering::Acquire),
13891        0,
13892        "Should not restart LSP server on an unrelated LSP settings change"
13893    );
13894
13895    update_test_project_settings(cx, |project_settings| {
13896        project_settings.lsp.insert(
13897            language_server_name.into(),
13898            LspSettings {
13899                binary: None,
13900                settings: None,
13901                initialization_options: Some(json!({
13902                    "anotherInitValue": false
13903                })),
13904                enable_lsp_tasks: false,
13905            },
13906        );
13907    });
13908    cx.executor().run_until_parked();
13909    assert_eq!(
13910        server_restarts.load(atomic::Ordering::Acquire),
13911        1,
13912        "Should restart LSP server on a related LSP settings change"
13913    );
13914
13915    update_test_project_settings(cx, |project_settings| {
13916        project_settings.lsp.insert(
13917            language_server_name.into(),
13918            LspSettings {
13919                binary: None,
13920                settings: None,
13921                initialization_options: Some(json!({
13922                    "anotherInitValue": false
13923                })),
13924                enable_lsp_tasks: false,
13925            },
13926        );
13927    });
13928    cx.executor().run_until_parked();
13929    assert_eq!(
13930        server_restarts.load(atomic::Ordering::Acquire),
13931        1,
13932        "Should not restart LSP server on a related LSP settings change that is the same"
13933    );
13934
13935    update_test_project_settings(cx, |project_settings| {
13936        project_settings.lsp.insert(
13937            language_server_name.into(),
13938            LspSettings {
13939                binary: None,
13940                settings: None,
13941                initialization_options: None,
13942                enable_lsp_tasks: false,
13943            },
13944        );
13945    });
13946    cx.executor().run_until_parked();
13947    assert_eq!(
13948        server_restarts.load(atomic::Ordering::Acquire),
13949        2,
13950        "Should restart LSP server on another related LSP settings change"
13951    );
13952}
13953
13954#[gpui::test]
13955async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
13956    init_test(cx, |_| {});
13957
13958    let mut cx = EditorLspTestContext::new_rust(
13959        lsp::ServerCapabilities {
13960            completion_provider: Some(lsp::CompletionOptions {
13961                trigger_characters: Some(vec![".".to_string()]),
13962                resolve_provider: Some(true),
13963                ..Default::default()
13964            }),
13965            ..Default::default()
13966        },
13967        cx,
13968    )
13969    .await;
13970
13971    cx.set_state("fn main() { let a = 2ˇ; }");
13972    cx.simulate_keystroke(".");
13973    let completion_item = lsp::CompletionItem {
13974        label: "some".into(),
13975        kind: Some(lsp::CompletionItemKind::SNIPPET),
13976        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13977        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13978            kind: lsp::MarkupKind::Markdown,
13979            value: "```rust\nSome(2)\n```".to_string(),
13980        })),
13981        deprecated: Some(false),
13982        sort_text: Some("fffffff2".to_string()),
13983        filter_text: Some("some".to_string()),
13984        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13985        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13986            range: lsp::Range {
13987                start: lsp::Position {
13988                    line: 0,
13989                    character: 22,
13990                },
13991                end: lsp::Position {
13992                    line: 0,
13993                    character: 22,
13994                },
13995            },
13996            new_text: "Some(2)".to_string(),
13997        })),
13998        additional_text_edits: Some(vec![lsp::TextEdit {
13999            range: lsp::Range {
14000                start: lsp::Position {
14001                    line: 0,
14002                    character: 20,
14003                },
14004                end: lsp::Position {
14005                    line: 0,
14006                    character: 22,
14007                },
14008            },
14009            new_text: "".to_string(),
14010        }]),
14011        ..Default::default()
14012    };
14013
14014    let closure_completion_item = completion_item.clone();
14015    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14016        let task_completion_item = closure_completion_item.clone();
14017        async move {
14018            Ok(Some(lsp::CompletionResponse::Array(vec![
14019                task_completion_item,
14020            ])))
14021        }
14022    });
14023
14024    request.next().await;
14025
14026    cx.condition(|editor, _| editor.context_menu_visible())
14027        .await;
14028    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14029        editor
14030            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14031            .unwrap()
14032    });
14033    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14034
14035    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14036        let task_completion_item = completion_item.clone();
14037        async move { Ok(task_completion_item) }
14038    })
14039    .next()
14040    .await
14041    .unwrap();
14042    apply_additional_edits.await.unwrap();
14043    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14044}
14045
14046#[gpui::test]
14047async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14048    init_test(cx, |_| {});
14049
14050    let mut cx = EditorLspTestContext::new_rust(
14051        lsp::ServerCapabilities {
14052            completion_provider: Some(lsp::CompletionOptions {
14053                trigger_characters: Some(vec![".".to_string()]),
14054                resolve_provider: Some(true),
14055                ..Default::default()
14056            }),
14057            ..Default::default()
14058        },
14059        cx,
14060    )
14061    .await;
14062
14063    cx.set_state("fn main() { let a = 2ˇ; }");
14064    cx.simulate_keystroke(".");
14065
14066    let item1 = lsp::CompletionItem {
14067        label: "method id()".to_string(),
14068        filter_text: Some("id".to_string()),
14069        detail: None,
14070        documentation: None,
14071        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14072            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14073            new_text: ".id".to_string(),
14074        })),
14075        ..lsp::CompletionItem::default()
14076    };
14077
14078    let item2 = lsp::CompletionItem {
14079        label: "other".to_string(),
14080        filter_text: Some("other".to_string()),
14081        detail: None,
14082        documentation: None,
14083        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14084            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14085            new_text: ".other".to_string(),
14086        })),
14087        ..lsp::CompletionItem::default()
14088    };
14089
14090    let item1 = item1.clone();
14091    cx.set_request_handler::<lsp::request::Completion, _, _>({
14092        let item1 = item1.clone();
14093        move |_, _, _| {
14094            let item1 = item1.clone();
14095            let item2 = item2.clone();
14096            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14097        }
14098    })
14099    .next()
14100    .await;
14101
14102    cx.condition(|editor, _| editor.context_menu_visible())
14103        .await;
14104    cx.update_editor(|editor, _, _| {
14105        let context_menu = editor.context_menu.borrow_mut();
14106        let context_menu = context_menu
14107            .as_ref()
14108            .expect("Should have the context menu deployed");
14109        match context_menu {
14110            CodeContextMenu::Completions(completions_menu) => {
14111                let completions = completions_menu.completions.borrow_mut();
14112                assert_eq!(
14113                    completions
14114                        .iter()
14115                        .map(|completion| &completion.label.text)
14116                        .collect::<Vec<_>>(),
14117                    vec!["method id()", "other"]
14118                )
14119            }
14120            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14121        }
14122    });
14123
14124    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14125        let item1 = item1.clone();
14126        move |_, item_to_resolve, _| {
14127            let item1 = item1.clone();
14128            async move {
14129                if item1 == item_to_resolve {
14130                    Ok(lsp::CompletionItem {
14131                        label: "method id()".to_string(),
14132                        filter_text: Some("id".to_string()),
14133                        detail: Some("Now resolved!".to_string()),
14134                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14135                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14136                            range: lsp::Range::new(
14137                                lsp::Position::new(0, 22),
14138                                lsp::Position::new(0, 22),
14139                            ),
14140                            new_text: ".id".to_string(),
14141                        })),
14142                        ..lsp::CompletionItem::default()
14143                    })
14144                } else {
14145                    Ok(item_to_resolve)
14146                }
14147            }
14148        }
14149    })
14150    .next()
14151    .await
14152    .unwrap();
14153    cx.run_until_parked();
14154
14155    cx.update_editor(|editor, window, cx| {
14156        editor.context_menu_next(&Default::default(), window, cx);
14157    });
14158
14159    cx.update_editor(|editor, _, _| {
14160        let context_menu = editor.context_menu.borrow_mut();
14161        let context_menu = context_menu
14162            .as_ref()
14163            .expect("Should have the context menu deployed");
14164        match context_menu {
14165            CodeContextMenu::Completions(completions_menu) => {
14166                let completions = completions_menu.completions.borrow_mut();
14167                assert_eq!(
14168                    completions
14169                        .iter()
14170                        .map(|completion| &completion.label.text)
14171                        .collect::<Vec<_>>(),
14172                    vec!["method id() Now resolved!", "other"],
14173                    "Should update first completion label, but not second as the filter text did not match."
14174                );
14175            }
14176            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14177        }
14178    });
14179}
14180
14181#[gpui::test]
14182async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14183    init_test(cx, |_| {});
14184    let mut cx = EditorLspTestContext::new_rust(
14185        lsp::ServerCapabilities {
14186            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14187            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14188            completion_provider: Some(lsp::CompletionOptions {
14189                resolve_provider: Some(true),
14190                ..Default::default()
14191            }),
14192            ..Default::default()
14193        },
14194        cx,
14195    )
14196    .await;
14197    cx.set_state(indoc! {"
14198        struct TestStruct {
14199            field: i32
14200        }
14201
14202        fn mainˇ() {
14203            let unused_var = 42;
14204            let test_struct = TestStruct { field: 42 };
14205        }
14206    "});
14207    let symbol_range = cx.lsp_range(indoc! {"
14208        struct TestStruct {
14209            field: i32
14210        }
14211
14212        «fn main»() {
14213            let unused_var = 42;
14214            let test_struct = TestStruct { field: 42 };
14215        }
14216    "});
14217    let mut hover_requests =
14218        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14219            Ok(Some(lsp::Hover {
14220                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14221                    kind: lsp::MarkupKind::Markdown,
14222                    value: "Function documentation".to_string(),
14223                }),
14224                range: Some(symbol_range),
14225            }))
14226        });
14227
14228    // Case 1: Test that code action menu hide hover popover
14229    cx.dispatch_action(Hover);
14230    hover_requests.next().await;
14231    cx.condition(|editor, _| editor.hover_state.visible()).await;
14232    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14233        move |_, _, _| async move {
14234            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14235                lsp::CodeAction {
14236                    title: "Remove unused variable".to_string(),
14237                    kind: Some(CodeActionKind::QUICKFIX),
14238                    edit: Some(lsp::WorkspaceEdit {
14239                        changes: Some(
14240                            [(
14241                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14242                                vec![lsp::TextEdit {
14243                                    range: lsp::Range::new(
14244                                        lsp::Position::new(5, 4),
14245                                        lsp::Position::new(5, 27),
14246                                    ),
14247                                    new_text: "".to_string(),
14248                                }],
14249                            )]
14250                            .into_iter()
14251                            .collect(),
14252                        ),
14253                        ..Default::default()
14254                    }),
14255                    ..Default::default()
14256                },
14257            )]))
14258        },
14259    );
14260    cx.update_editor(|editor, window, cx| {
14261        editor.toggle_code_actions(
14262            &ToggleCodeActions {
14263                deployed_from: None,
14264                quick_launch: false,
14265            },
14266            window,
14267            cx,
14268        );
14269    });
14270    code_action_requests.next().await;
14271    cx.run_until_parked();
14272    cx.condition(|editor, _| editor.context_menu_visible())
14273        .await;
14274    cx.update_editor(|editor, _, _| {
14275        assert!(
14276            !editor.hover_state.visible(),
14277            "Hover popover should be hidden when code action menu is shown"
14278        );
14279        // Hide code actions
14280        editor.context_menu.take();
14281    });
14282
14283    // Case 2: Test that code completions hide hover popover
14284    cx.dispatch_action(Hover);
14285    hover_requests.next().await;
14286    cx.condition(|editor, _| editor.hover_state.visible()).await;
14287    let counter = Arc::new(AtomicUsize::new(0));
14288    let mut completion_requests =
14289        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14290            let counter = counter.clone();
14291            async move {
14292                counter.fetch_add(1, atomic::Ordering::Release);
14293                Ok(Some(lsp::CompletionResponse::Array(vec![
14294                    lsp::CompletionItem {
14295                        label: "main".into(),
14296                        kind: Some(lsp::CompletionItemKind::FUNCTION),
14297                        detail: Some("() -> ()".to_string()),
14298                        ..Default::default()
14299                    },
14300                    lsp::CompletionItem {
14301                        label: "TestStruct".into(),
14302                        kind: Some(lsp::CompletionItemKind::STRUCT),
14303                        detail: Some("struct TestStruct".to_string()),
14304                        ..Default::default()
14305                    },
14306                ])))
14307            }
14308        });
14309    cx.update_editor(|editor, window, cx| {
14310        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14311    });
14312    completion_requests.next().await;
14313    cx.condition(|editor, _| editor.context_menu_visible())
14314        .await;
14315    cx.update_editor(|editor, _, _| {
14316        assert!(
14317            !editor.hover_state.visible(),
14318            "Hover popover should be hidden when completion menu is shown"
14319        );
14320    });
14321}
14322
14323#[gpui::test]
14324async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
14325    init_test(cx, |_| {});
14326
14327    let mut cx = EditorLspTestContext::new_rust(
14328        lsp::ServerCapabilities {
14329            completion_provider: Some(lsp::CompletionOptions {
14330                trigger_characters: Some(vec![".".to_string()]),
14331                resolve_provider: Some(true),
14332                ..Default::default()
14333            }),
14334            ..Default::default()
14335        },
14336        cx,
14337    )
14338    .await;
14339
14340    cx.set_state("fn main() { let a = 2ˇ; }");
14341    cx.simulate_keystroke(".");
14342
14343    let unresolved_item_1 = lsp::CompletionItem {
14344        label: "id".to_string(),
14345        filter_text: Some("id".to_string()),
14346        detail: None,
14347        documentation: None,
14348        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14349            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14350            new_text: ".id".to_string(),
14351        })),
14352        ..lsp::CompletionItem::default()
14353    };
14354    let resolved_item_1 = lsp::CompletionItem {
14355        additional_text_edits: Some(vec![lsp::TextEdit {
14356            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14357            new_text: "!!".to_string(),
14358        }]),
14359        ..unresolved_item_1.clone()
14360    };
14361    let unresolved_item_2 = lsp::CompletionItem {
14362        label: "other".to_string(),
14363        filter_text: Some("other".to_string()),
14364        detail: None,
14365        documentation: None,
14366        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14367            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14368            new_text: ".other".to_string(),
14369        })),
14370        ..lsp::CompletionItem::default()
14371    };
14372    let resolved_item_2 = lsp::CompletionItem {
14373        additional_text_edits: Some(vec![lsp::TextEdit {
14374            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
14375            new_text: "??".to_string(),
14376        }]),
14377        ..unresolved_item_2.clone()
14378    };
14379
14380    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
14381    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
14382    cx.lsp
14383        .server
14384        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14385            let unresolved_item_1 = unresolved_item_1.clone();
14386            let resolved_item_1 = resolved_item_1.clone();
14387            let unresolved_item_2 = unresolved_item_2.clone();
14388            let resolved_item_2 = resolved_item_2.clone();
14389            let resolve_requests_1 = resolve_requests_1.clone();
14390            let resolve_requests_2 = resolve_requests_2.clone();
14391            move |unresolved_request, _| {
14392                let unresolved_item_1 = unresolved_item_1.clone();
14393                let resolved_item_1 = resolved_item_1.clone();
14394                let unresolved_item_2 = unresolved_item_2.clone();
14395                let resolved_item_2 = resolved_item_2.clone();
14396                let resolve_requests_1 = resolve_requests_1.clone();
14397                let resolve_requests_2 = resolve_requests_2.clone();
14398                async move {
14399                    if unresolved_request == unresolved_item_1 {
14400                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
14401                        Ok(resolved_item_1.clone())
14402                    } else if unresolved_request == unresolved_item_2 {
14403                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
14404                        Ok(resolved_item_2.clone())
14405                    } else {
14406                        panic!("Unexpected completion item {unresolved_request:?}")
14407                    }
14408                }
14409            }
14410        })
14411        .detach();
14412
14413    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14414        let unresolved_item_1 = unresolved_item_1.clone();
14415        let unresolved_item_2 = unresolved_item_2.clone();
14416        async move {
14417            Ok(Some(lsp::CompletionResponse::Array(vec![
14418                unresolved_item_1,
14419                unresolved_item_2,
14420            ])))
14421        }
14422    })
14423    .next()
14424    .await;
14425
14426    cx.condition(|editor, _| editor.context_menu_visible())
14427        .await;
14428    cx.update_editor(|editor, _, _| {
14429        let context_menu = editor.context_menu.borrow_mut();
14430        let context_menu = context_menu
14431            .as_ref()
14432            .expect("Should have the context menu deployed");
14433        match context_menu {
14434            CodeContextMenu::Completions(completions_menu) => {
14435                let completions = completions_menu.completions.borrow_mut();
14436                assert_eq!(
14437                    completions
14438                        .iter()
14439                        .map(|completion| &completion.label.text)
14440                        .collect::<Vec<_>>(),
14441                    vec!["id", "other"]
14442                )
14443            }
14444            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14445        }
14446    });
14447    cx.run_until_parked();
14448
14449    cx.update_editor(|editor, window, cx| {
14450        editor.context_menu_next(&ContextMenuNext, window, cx);
14451    });
14452    cx.run_until_parked();
14453    cx.update_editor(|editor, window, cx| {
14454        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14455    });
14456    cx.run_until_parked();
14457    cx.update_editor(|editor, window, cx| {
14458        editor.context_menu_next(&ContextMenuNext, window, cx);
14459    });
14460    cx.run_until_parked();
14461    cx.update_editor(|editor, window, cx| {
14462        editor
14463            .compose_completion(&ComposeCompletion::default(), window, cx)
14464            .expect("No task returned")
14465    })
14466    .await
14467    .expect("Completion failed");
14468    cx.run_until_parked();
14469
14470    cx.update_editor(|editor, _, cx| {
14471        assert_eq!(
14472            resolve_requests_1.load(atomic::Ordering::Acquire),
14473            1,
14474            "Should always resolve once despite multiple selections"
14475        );
14476        assert_eq!(
14477            resolve_requests_2.load(atomic::Ordering::Acquire),
14478            1,
14479            "Should always resolve once after multiple selections and applying the completion"
14480        );
14481        assert_eq!(
14482            editor.text(cx),
14483            "fn main() { let a = ??.other; }",
14484            "Should use resolved data when applying the completion"
14485        );
14486    });
14487}
14488
14489#[gpui::test]
14490async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
14491    init_test(cx, |_| {});
14492
14493    let item_0 = lsp::CompletionItem {
14494        label: "abs".into(),
14495        insert_text: Some("abs".into()),
14496        data: Some(json!({ "very": "special"})),
14497        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
14498        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14499            lsp::InsertReplaceEdit {
14500                new_text: "abs".to_string(),
14501                insert: lsp::Range::default(),
14502                replace: lsp::Range::default(),
14503            },
14504        )),
14505        ..lsp::CompletionItem::default()
14506    };
14507    let items = iter::once(item_0.clone())
14508        .chain((11..51).map(|i| lsp::CompletionItem {
14509            label: format!("item_{}", i),
14510            insert_text: Some(format!("item_{}", i)),
14511            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14512            ..lsp::CompletionItem::default()
14513        }))
14514        .collect::<Vec<_>>();
14515
14516    let default_commit_characters = vec!["?".to_string()];
14517    let default_data = json!({ "default": "data"});
14518    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
14519    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
14520    let default_edit_range = lsp::Range {
14521        start: lsp::Position {
14522            line: 0,
14523            character: 5,
14524        },
14525        end: lsp::Position {
14526            line: 0,
14527            character: 5,
14528        },
14529    };
14530
14531    let mut cx = EditorLspTestContext::new_rust(
14532        lsp::ServerCapabilities {
14533            completion_provider: Some(lsp::CompletionOptions {
14534                trigger_characters: Some(vec![".".to_string()]),
14535                resolve_provider: Some(true),
14536                ..Default::default()
14537            }),
14538            ..Default::default()
14539        },
14540        cx,
14541    )
14542    .await;
14543
14544    cx.set_state("fn main() { let a = 2ˇ; }");
14545    cx.simulate_keystroke(".");
14546
14547    let completion_data = default_data.clone();
14548    let completion_characters = default_commit_characters.clone();
14549    let completion_items = items.clone();
14550    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14551        let default_data = completion_data.clone();
14552        let default_commit_characters = completion_characters.clone();
14553        let items = completion_items.clone();
14554        async move {
14555            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14556                items,
14557                item_defaults: Some(lsp::CompletionListItemDefaults {
14558                    data: Some(default_data.clone()),
14559                    commit_characters: Some(default_commit_characters.clone()),
14560                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
14561                        default_edit_range,
14562                    )),
14563                    insert_text_format: Some(default_insert_text_format),
14564                    insert_text_mode: Some(default_insert_text_mode),
14565                }),
14566                ..lsp::CompletionList::default()
14567            })))
14568        }
14569    })
14570    .next()
14571    .await;
14572
14573    let resolved_items = Arc::new(Mutex::new(Vec::new()));
14574    cx.lsp
14575        .server
14576        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
14577            let closure_resolved_items = resolved_items.clone();
14578            move |item_to_resolve, _| {
14579                let closure_resolved_items = closure_resolved_items.clone();
14580                async move {
14581                    closure_resolved_items.lock().push(item_to_resolve.clone());
14582                    Ok(item_to_resolve)
14583                }
14584            }
14585        })
14586        .detach();
14587
14588    cx.condition(|editor, _| editor.context_menu_visible())
14589        .await;
14590    cx.run_until_parked();
14591    cx.update_editor(|editor, _, _| {
14592        let menu = editor.context_menu.borrow_mut();
14593        match menu.as_ref().expect("should have the completions menu") {
14594            CodeContextMenu::Completions(completions_menu) => {
14595                assert_eq!(
14596                    completions_menu
14597                        .entries
14598                        .borrow()
14599                        .iter()
14600                        .map(|mat| mat.string.clone())
14601                        .collect::<Vec<String>>(),
14602                    items
14603                        .iter()
14604                        .map(|completion| completion.label.clone())
14605                        .collect::<Vec<String>>()
14606                );
14607            }
14608            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
14609        }
14610    });
14611    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
14612    // with 4 from the end.
14613    assert_eq!(
14614        *resolved_items.lock(),
14615        [&items[0..16], &items[items.len() - 4..items.len()]]
14616            .concat()
14617            .iter()
14618            .cloned()
14619            .map(|mut item| {
14620                if item.data.is_none() {
14621                    item.data = Some(default_data.clone());
14622                }
14623                item
14624            })
14625            .collect::<Vec<lsp::CompletionItem>>(),
14626        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
14627    );
14628    resolved_items.lock().clear();
14629
14630    cx.update_editor(|editor, window, cx| {
14631        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
14632    });
14633    cx.run_until_parked();
14634    // Completions that have already been resolved are skipped.
14635    assert_eq!(
14636        *resolved_items.lock(),
14637        items[items.len() - 16..items.len() - 4]
14638            .iter()
14639            .cloned()
14640            .map(|mut item| {
14641                if item.data.is_none() {
14642                    item.data = Some(default_data.clone());
14643                }
14644                item
14645            })
14646            .collect::<Vec<lsp::CompletionItem>>()
14647    );
14648    resolved_items.lock().clear();
14649}
14650
14651#[gpui::test]
14652async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
14653    init_test(cx, |_| {});
14654
14655    let mut cx = EditorLspTestContext::new(
14656        Language::new(
14657            LanguageConfig {
14658                matcher: LanguageMatcher {
14659                    path_suffixes: vec!["jsx".into()],
14660                    ..Default::default()
14661                },
14662                overrides: [(
14663                    "element".into(),
14664                    LanguageConfigOverride {
14665                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
14666                        ..Default::default()
14667                    },
14668                )]
14669                .into_iter()
14670                .collect(),
14671                ..Default::default()
14672            },
14673            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14674        )
14675        .with_override_query("(jsx_self_closing_element) @element")
14676        .unwrap(),
14677        lsp::ServerCapabilities {
14678            completion_provider: Some(lsp::CompletionOptions {
14679                trigger_characters: Some(vec![":".to_string()]),
14680                ..Default::default()
14681            }),
14682            ..Default::default()
14683        },
14684        cx,
14685    )
14686    .await;
14687
14688    cx.lsp
14689        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14690            Ok(Some(lsp::CompletionResponse::Array(vec![
14691                lsp::CompletionItem {
14692                    label: "bg-blue".into(),
14693                    ..Default::default()
14694                },
14695                lsp::CompletionItem {
14696                    label: "bg-red".into(),
14697                    ..Default::default()
14698                },
14699                lsp::CompletionItem {
14700                    label: "bg-yellow".into(),
14701                    ..Default::default()
14702                },
14703            ])))
14704        });
14705
14706    cx.set_state(r#"<p class="bgˇ" />"#);
14707
14708    // Trigger completion when typing a dash, because the dash is an extra
14709    // word character in the 'element' scope, which contains the cursor.
14710    cx.simulate_keystroke("-");
14711    cx.executor().run_until_parked();
14712    cx.update_editor(|editor, _, _| {
14713        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14714        {
14715            assert_eq!(
14716                completion_menu_entries(&menu),
14717                &["bg-red", "bg-blue", "bg-yellow"]
14718            );
14719        } else {
14720            panic!("expected completion menu to be open");
14721        }
14722    });
14723
14724    cx.simulate_keystroke("l");
14725    cx.executor().run_until_parked();
14726    cx.update_editor(|editor, _, _| {
14727        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14728        {
14729            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
14730        } else {
14731            panic!("expected completion menu to be open");
14732        }
14733    });
14734
14735    // When filtering completions, consider the character after the '-' to
14736    // be the start of a subword.
14737    cx.set_state(r#"<p class="yelˇ" />"#);
14738    cx.simulate_keystroke("l");
14739    cx.executor().run_until_parked();
14740    cx.update_editor(|editor, _, _| {
14741        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14742        {
14743            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
14744        } else {
14745            panic!("expected completion menu to be open");
14746        }
14747    });
14748}
14749
14750fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
14751    let entries = menu.entries.borrow();
14752    entries.iter().map(|mat| mat.string.clone()).collect()
14753}
14754
14755#[gpui::test]
14756async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
14757    init_test(cx, |settings| {
14758        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
14759            FormatterList(vec![Formatter::Prettier].into()),
14760        ))
14761    });
14762
14763    let fs = FakeFs::new(cx.executor());
14764    fs.insert_file(path!("/file.ts"), Default::default()).await;
14765
14766    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
14767    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14768
14769    language_registry.add(Arc::new(Language::new(
14770        LanguageConfig {
14771            name: "TypeScript".into(),
14772            matcher: LanguageMatcher {
14773                path_suffixes: vec!["ts".to_string()],
14774                ..Default::default()
14775            },
14776            ..Default::default()
14777        },
14778        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14779    )));
14780    update_test_language_settings(cx, |settings| {
14781        settings.defaults.prettier = Some(PrettierSettings {
14782            allowed: true,
14783            ..PrettierSettings::default()
14784        });
14785    });
14786
14787    let test_plugin = "test_plugin";
14788    let _ = language_registry.register_fake_lsp(
14789        "TypeScript",
14790        FakeLspAdapter {
14791            prettier_plugins: vec![test_plugin],
14792            ..Default::default()
14793        },
14794    );
14795
14796    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
14797    let buffer = project
14798        .update(cx, |project, cx| {
14799            project.open_local_buffer(path!("/file.ts"), cx)
14800        })
14801        .await
14802        .unwrap();
14803
14804    let buffer_text = "one\ntwo\nthree\n";
14805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14806    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14807    editor.update_in(cx, |editor, window, cx| {
14808        editor.set_text(buffer_text, window, cx)
14809    });
14810
14811    editor
14812        .update_in(cx, |editor, window, cx| {
14813            editor.perform_format(
14814                project.clone(),
14815                FormatTrigger::Manual,
14816                FormatTarget::Buffers,
14817                window,
14818                cx,
14819            )
14820        })
14821        .unwrap()
14822        .await;
14823    assert_eq!(
14824        editor.update(cx, |editor, cx| editor.text(cx)),
14825        buffer_text.to_string() + prettier_format_suffix,
14826        "Test prettier formatting was not applied to the original buffer text",
14827    );
14828
14829    update_test_language_settings(cx, |settings| {
14830        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
14831    });
14832    let format = editor.update_in(cx, |editor, window, cx| {
14833        editor.perform_format(
14834            project.clone(),
14835            FormatTrigger::Manual,
14836            FormatTarget::Buffers,
14837            window,
14838            cx,
14839        )
14840    });
14841    format.await.unwrap();
14842    assert_eq!(
14843        editor.update(cx, |editor, cx| editor.text(cx)),
14844        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
14845        "Autoformatting (via test prettier) was not applied to the original buffer text",
14846    );
14847}
14848
14849#[gpui::test]
14850async fn test_addition_reverts(cx: &mut TestAppContext) {
14851    init_test(cx, |_| {});
14852    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14853    let base_text = indoc! {r#"
14854        struct Row;
14855        struct Row1;
14856        struct Row2;
14857
14858        struct Row4;
14859        struct Row5;
14860        struct Row6;
14861
14862        struct Row8;
14863        struct Row9;
14864        struct Row10;"#};
14865
14866    // When addition hunks are not adjacent to carets, no hunk revert is performed
14867    assert_hunk_revert(
14868        indoc! {r#"struct Row;
14869                   struct Row1;
14870                   struct Row1.1;
14871                   struct Row1.2;
14872                   struct Row2;ˇ
14873
14874                   struct Row4;
14875                   struct Row5;
14876                   struct Row6;
14877
14878                   struct Row8;
14879                   ˇstruct Row9;
14880                   struct Row9.1;
14881                   struct Row9.2;
14882                   struct Row9.3;
14883                   struct Row10;"#},
14884        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14885        indoc! {r#"struct Row;
14886                   struct Row1;
14887                   struct Row1.1;
14888                   struct Row1.2;
14889                   struct Row2;ˇ
14890
14891                   struct Row4;
14892                   struct Row5;
14893                   struct Row6;
14894
14895                   struct Row8;
14896                   ˇstruct Row9;
14897                   struct Row9.1;
14898                   struct Row9.2;
14899                   struct Row9.3;
14900                   struct Row10;"#},
14901        base_text,
14902        &mut cx,
14903    );
14904    // Same for selections
14905    assert_hunk_revert(
14906        indoc! {r#"struct Row;
14907                   struct Row1;
14908                   struct Row2;
14909                   struct Row2.1;
14910                   struct Row2.2;
14911                   «ˇ
14912                   struct Row4;
14913                   struct» Row5;
14914                   «struct Row6;
14915                   ˇ»
14916                   struct Row9.1;
14917                   struct Row9.2;
14918                   struct Row9.3;
14919                   struct Row8;
14920                   struct Row9;
14921                   struct Row10;"#},
14922        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
14923        indoc! {r#"struct Row;
14924                   struct Row1;
14925                   struct Row2;
14926                   struct Row2.1;
14927                   struct Row2.2;
14928                   «ˇ
14929                   struct Row4;
14930                   struct» Row5;
14931                   «struct Row6;
14932                   ˇ»
14933                   struct Row9.1;
14934                   struct Row9.2;
14935                   struct Row9.3;
14936                   struct Row8;
14937                   struct Row9;
14938                   struct Row10;"#},
14939        base_text,
14940        &mut cx,
14941    );
14942
14943    // When carets and selections intersect the addition hunks, those are reverted.
14944    // Adjacent carets got merged.
14945    assert_hunk_revert(
14946        indoc! {r#"struct Row;
14947                   ˇ// something on the top
14948                   struct Row1;
14949                   struct Row2;
14950                   struct Roˇw3.1;
14951                   struct Row2.2;
14952                   struct Row2.3;ˇ
14953
14954                   struct Row4;
14955                   struct ˇRow5.1;
14956                   struct Row5.2;
14957                   struct «Rowˇ»5.3;
14958                   struct Row5;
14959                   struct Row6;
14960                   ˇ
14961                   struct Row9.1;
14962                   struct «Rowˇ»9.2;
14963                   struct «ˇRow»9.3;
14964                   struct Row8;
14965                   struct Row9;
14966                   «ˇ// something on bottom»
14967                   struct Row10;"#},
14968        vec![
14969            DiffHunkStatusKind::Added,
14970            DiffHunkStatusKind::Added,
14971            DiffHunkStatusKind::Added,
14972            DiffHunkStatusKind::Added,
14973            DiffHunkStatusKind::Added,
14974        ],
14975        indoc! {r#"struct Row;
14976                   ˇstruct Row1;
14977                   struct Row2;
14978                   ˇ
14979                   struct Row4;
14980                   ˇstruct Row5;
14981                   struct Row6;
14982                   ˇ
14983                   ˇstruct Row8;
14984                   struct Row9;
14985                   ˇstruct Row10;"#},
14986        base_text,
14987        &mut cx,
14988    );
14989}
14990
14991#[gpui::test]
14992async fn test_modification_reverts(cx: &mut TestAppContext) {
14993    init_test(cx, |_| {});
14994    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14995    let base_text = indoc! {r#"
14996        struct Row;
14997        struct Row1;
14998        struct Row2;
14999
15000        struct Row4;
15001        struct Row5;
15002        struct Row6;
15003
15004        struct Row8;
15005        struct Row9;
15006        struct Row10;"#};
15007
15008    // Modification hunks behave the same as the addition ones.
15009    assert_hunk_revert(
15010        indoc! {r#"struct Row;
15011                   struct Row1;
15012                   struct Row33;
15013                   ˇ
15014                   struct Row4;
15015                   struct Row5;
15016                   struct Row6;
15017                   ˇ
15018                   struct Row99;
15019                   struct Row9;
15020                   struct Row10;"#},
15021        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15022        indoc! {r#"struct Row;
15023                   struct Row1;
15024                   struct Row33;
15025                   ˇ
15026                   struct Row4;
15027                   struct Row5;
15028                   struct Row6;
15029                   ˇ
15030                   struct Row99;
15031                   struct Row9;
15032                   struct Row10;"#},
15033        base_text,
15034        &mut cx,
15035    );
15036    assert_hunk_revert(
15037        indoc! {r#"struct Row;
15038                   struct Row1;
15039                   struct Row33;
15040                   «ˇ
15041                   struct Row4;
15042                   struct» Row5;
15043                   «struct Row6;
15044                   ˇ»
15045                   struct Row99;
15046                   struct Row9;
15047                   struct Row10;"#},
15048        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15049        indoc! {r#"struct Row;
15050                   struct Row1;
15051                   struct Row33;
15052                   «ˇ
15053                   struct Row4;
15054                   struct» Row5;
15055                   «struct Row6;
15056                   ˇ»
15057                   struct Row99;
15058                   struct Row9;
15059                   struct Row10;"#},
15060        base_text,
15061        &mut cx,
15062    );
15063
15064    assert_hunk_revert(
15065        indoc! {r#"ˇstruct Row1.1;
15066                   struct Row1;
15067                   «ˇstr»uct Row22;
15068
15069                   struct ˇRow44;
15070                   struct Row5;
15071                   struct «Rˇ»ow66;ˇ
15072
15073                   «struˇ»ct Row88;
15074                   struct Row9;
15075                   struct Row1011;ˇ"#},
15076        vec![
15077            DiffHunkStatusKind::Modified,
15078            DiffHunkStatusKind::Modified,
15079            DiffHunkStatusKind::Modified,
15080            DiffHunkStatusKind::Modified,
15081            DiffHunkStatusKind::Modified,
15082            DiffHunkStatusKind::Modified,
15083        ],
15084        indoc! {r#"struct Row;
15085                   ˇstruct Row1;
15086                   struct Row2;
15087                   ˇ
15088                   struct Row4;
15089                   ˇstruct Row5;
15090                   struct Row6;
15091                   ˇ
15092                   struct Row8;
15093                   ˇstruct Row9;
15094                   struct Row10;ˇ"#},
15095        base_text,
15096        &mut cx,
15097    );
15098}
15099
15100#[gpui::test]
15101async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15102    init_test(cx, |_| {});
15103    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15104    let base_text = indoc! {r#"
15105        one
15106
15107        two
15108        three
15109        "#};
15110
15111    cx.set_head_text(base_text);
15112    cx.set_state("\nˇ\n");
15113    cx.executor().run_until_parked();
15114    cx.update_editor(|editor, _window, cx| {
15115        editor.expand_selected_diff_hunks(cx);
15116    });
15117    cx.executor().run_until_parked();
15118    cx.update_editor(|editor, window, cx| {
15119        editor.backspace(&Default::default(), window, cx);
15120    });
15121    cx.run_until_parked();
15122    cx.assert_state_with_diff(
15123        indoc! {r#"
15124
15125        - two
15126        - threeˇ
15127        +
15128        "#}
15129        .to_string(),
15130    );
15131}
15132
15133#[gpui::test]
15134async fn test_deletion_reverts(cx: &mut TestAppContext) {
15135    init_test(cx, |_| {});
15136    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15137    let base_text = indoc! {r#"struct Row;
15138struct Row1;
15139struct Row2;
15140
15141struct Row4;
15142struct Row5;
15143struct Row6;
15144
15145struct Row8;
15146struct Row9;
15147struct Row10;"#};
15148
15149    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15150    assert_hunk_revert(
15151        indoc! {r#"struct Row;
15152                   struct Row2;
15153
15154                   ˇstruct Row4;
15155                   struct Row5;
15156                   struct Row6;
15157                   ˇ
15158                   struct Row8;
15159                   struct Row10;"#},
15160        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15161        indoc! {r#"struct Row;
15162                   struct Row2;
15163
15164                   ˇstruct Row4;
15165                   struct Row5;
15166                   struct Row6;
15167                   ˇ
15168                   struct Row8;
15169                   struct Row10;"#},
15170        base_text,
15171        &mut cx,
15172    );
15173    assert_hunk_revert(
15174        indoc! {r#"struct Row;
15175                   struct Row2;
15176
15177                   «ˇstruct Row4;
15178                   struct» Row5;
15179                   «struct Row6;
15180                   ˇ»
15181                   struct Row8;
15182                   struct Row10;"#},
15183        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15184        indoc! {r#"struct Row;
15185                   struct Row2;
15186
15187                   «ˇstruct Row4;
15188                   struct» Row5;
15189                   «struct Row6;
15190                   ˇ»
15191                   struct Row8;
15192                   struct Row10;"#},
15193        base_text,
15194        &mut cx,
15195    );
15196
15197    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15198    assert_hunk_revert(
15199        indoc! {r#"struct Row;
15200                   ˇstruct Row2;
15201
15202                   struct Row4;
15203                   struct Row5;
15204                   struct Row6;
15205
15206                   struct Row8;ˇ
15207                   struct Row10;"#},
15208        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15209        indoc! {r#"struct Row;
15210                   struct Row1;
15211                   ˇstruct Row2;
15212
15213                   struct Row4;
15214                   struct Row5;
15215                   struct Row6;
15216
15217                   struct Row8;ˇ
15218                   struct Row9;
15219                   struct Row10;"#},
15220        base_text,
15221        &mut cx,
15222    );
15223    assert_hunk_revert(
15224        indoc! {r#"struct Row;
15225                   struct Row2«ˇ;
15226                   struct Row4;
15227                   struct» Row5;
15228                   «struct Row6;
15229
15230                   struct Row8;ˇ»
15231                   struct Row10;"#},
15232        vec![
15233            DiffHunkStatusKind::Deleted,
15234            DiffHunkStatusKind::Deleted,
15235            DiffHunkStatusKind::Deleted,
15236        ],
15237        indoc! {r#"struct Row;
15238                   struct Row1;
15239                   struct Row2«ˇ;
15240
15241                   struct Row4;
15242                   struct» Row5;
15243                   «struct Row6;
15244
15245                   struct Row8;ˇ»
15246                   struct Row9;
15247                   struct Row10;"#},
15248        base_text,
15249        &mut cx,
15250    );
15251}
15252
15253#[gpui::test]
15254async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
15255    init_test(cx, |_| {});
15256
15257    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
15258    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
15259    let base_text_3 =
15260        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
15261
15262    let text_1 = edit_first_char_of_every_line(base_text_1);
15263    let text_2 = edit_first_char_of_every_line(base_text_2);
15264    let text_3 = edit_first_char_of_every_line(base_text_3);
15265
15266    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
15267    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
15268    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
15269
15270    let multibuffer = cx.new(|cx| {
15271        let mut multibuffer = MultiBuffer::new(ReadWrite);
15272        multibuffer.push_excerpts(
15273            buffer_1.clone(),
15274            [
15275                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15276                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15277                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15278            ],
15279            cx,
15280        );
15281        multibuffer.push_excerpts(
15282            buffer_2.clone(),
15283            [
15284                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15285                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15286                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15287            ],
15288            cx,
15289        );
15290        multibuffer.push_excerpts(
15291            buffer_3.clone(),
15292            [
15293                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15294                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15295                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15296            ],
15297            cx,
15298        );
15299        multibuffer
15300    });
15301
15302    let fs = FakeFs::new(cx.executor());
15303    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
15304    let (editor, cx) = cx
15305        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
15306    editor.update_in(cx, |editor, _window, cx| {
15307        for (buffer, diff_base) in [
15308            (buffer_1.clone(), base_text_1),
15309            (buffer_2.clone(), base_text_2),
15310            (buffer_3.clone(), base_text_3),
15311        ] {
15312            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15313            editor
15314                .buffer
15315                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15316        }
15317    });
15318    cx.executor().run_until_parked();
15319
15320    editor.update_in(cx, |editor, window, cx| {
15321        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}");
15322        editor.select_all(&SelectAll, window, cx);
15323        editor.git_restore(&Default::default(), window, cx);
15324    });
15325    cx.executor().run_until_parked();
15326
15327    // When all ranges are selected, all buffer hunks are reverted.
15328    editor.update(cx, |editor, cx| {
15329        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");
15330    });
15331    buffer_1.update(cx, |buffer, _| {
15332        assert_eq!(buffer.text(), base_text_1);
15333    });
15334    buffer_2.update(cx, |buffer, _| {
15335        assert_eq!(buffer.text(), base_text_2);
15336    });
15337    buffer_3.update(cx, |buffer, _| {
15338        assert_eq!(buffer.text(), base_text_3);
15339    });
15340
15341    editor.update_in(cx, |editor, window, cx| {
15342        editor.undo(&Default::default(), window, cx);
15343    });
15344
15345    editor.update_in(cx, |editor, window, cx| {
15346        editor.change_selections(None, window, cx, |s| {
15347            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
15348        });
15349        editor.git_restore(&Default::default(), window, cx);
15350    });
15351
15352    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
15353    // but not affect buffer_2 and its related excerpts.
15354    editor.update(cx, |editor, cx| {
15355        assert_eq!(
15356            editor.text(cx),
15357            "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}"
15358        );
15359    });
15360    buffer_1.update(cx, |buffer, _| {
15361        assert_eq!(buffer.text(), base_text_1);
15362    });
15363    buffer_2.update(cx, |buffer, _| {
15364        assert_eq!(
15365            buffer.text(),
15366            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
15367        );
15368    });
15369    buffer_3.update(cx, |buffer, _| {
15370        assert_eq!(
15371            buffer.text(),
15372            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
15373        );
15374    });
15375
15376    fn edit_first_char_of_every_line(text: &str) -> String {
15377        text.split('\n')
15378            .map(|line| format!("X{}", &line[1..]))
15379            .collect::<Vec<_>>()
15380            .join("\n")
15381    }
15382}
15383
15384#[gpui::test]
15385async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
15386    init_test(cx, |_| {});
15387
15388    let cols = 4;
15389    let rows = 10;
15390    let sample_text_1 = sample_text(rows, cols, 'a');
15391    assert_eq!(
15392        sample_text_1,
15393        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
15394    );
15395    let sample_text_2 = sample_text(rows, cols, 'l');
15396    assert_eq!(
15397        sample_text_2,
15398        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
15399    );
15400    let sample_text_3 = sample_text(rows, cols, 'v');
15401    assert_eq!(
15402        sample_text_3,
15403        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
15404    );
15405
15406    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
15407    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
15408    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
15409
15410    let multi_buffer = cx.new(|cx| {
15411        let mut multibuffer = MultiBuffer::new(ReadWrite);
15412        multibuffer.push_excerpts(
15413            buffer_1.clone(),
15414            [
15415                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15416                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15417                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15418            ],
15419            cx,
15420        );
15421        multibuffer.push_excerpts(
15422            buffer_2.clone(),
15423            [
15424                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15425                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15426                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15427            ],
15428            cx,
15429        );
15430        multibuffer.push_excerpts(
15431            buffer_3.clone(),
15432            [
15433                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15434                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15435                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
15436            ],
15437            cx,
15438        );
15439        multibuffer
15440    });
15441
15442    let fs = FakeFs::new(cx.executor());
15443    fs.insert_tree(
15444        "/a",
15445        json!({
15446            "main.rs": sample_text_1,
15447            "other.rs": sample_text_2,
15448            "lib.rs": sample_text_3,
15449        }),
15450    )
15451    .await;
15452    let project = Project::test(fs, ["/a".as_ref()], cx).await;
15453    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15454    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15455    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15456        Editor::new(
15457            EditorMode::full(),
15458            multi_buffer,
15459            Some(project.clone()),
15460            window,
15461            cx,
15462        )
15463    });
15464    let multibuffer_item_id = workspace
15465        .update(cx, |workspace, window, cx| {
15466            assert!(
15467                workspace.active_item(cx).is_none(),
15468                "active item should be None before the first item is added"
15469            );
15470            workspace.add_item_to_active_pane(
15471                Box::new(multi_buffer_editor.clone()),
15472                None,
15473                true,
15474                window,
15475                cx,
15476            );
15477            let active_item = workspace
15478                .active_item(cx)
15479                .expect("should have an active item after adding the multi buffer");
15480            assert!(
15481                !active_item.is_singleton(cx),
15482                "A multi buffer was expected to active after adding"
15483            );
15484            active_item.item_id()
15485        })
15486        .unwrap();
15487    cx.executor().run_until_parked();
15488
15489    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15490        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15491            s.select_ranges(Some(1..2))
15492        });
15493        editor.open_excerpts(&OpenExcerpts, window, cx);
15494    });
15495    cx.executor().run_until_parked();
15496    let first_item_id = workspace
15497        .update(cx, |workspace, window, cx| {
15498            let active_item = workspace
15499                .active_item(cx)
15500                .expect("should have an active item after navigating into the 1st buffer");
15501            let first_item_id = active_item.item_id();
15502            assert_ne!(
15503                first_item_id, multibuffer_item_id,
15504                "Should navigate into the 1st buffer and activate it"
15505            );
15506            assert!(
15507                active_item.is_singleton(cx),
15508                "New active item should be a singleton buffer"
15509            );
15510            assert_eq!(
15511                active_item
15512                    .act_as::<Editor>(cx)
15513                    .expect("should have navigated into an editor for the 1st buffer")
15514                    .read(cx)
15515                    .text(cx),
15516                sample_text_1
15517            );
15518
15519            workspace
15520                .go_back(workspace.active_pane().downgrade(), window, cx)
15521                .detach_and_log_err(cx);
15522
15523            first_item_id
15524        })
15525        .unwrap();
15526    cx.executor().run_until_parked();
15527    workspace
15528        .update(cx, |workspace, _, cx| {
15529            let active_item = workspace
15530                .active_item(cx)
15531                .expect("should have an active item after navigating back");
15532            assert_eq!(
15533                active_item.item_id(),
15534                multibuffer_item_id,
15535                "Should navigate back to the multi buffer"
15536            );
15537            assert!(!active_item.is_singleton(cx));
15538        })
15539        .unwrap();
15540
15541    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15542        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15543            s.select_ranges(Some(39..40))
15544        });
15545        editor.open_excerpts(&OpenExcerpts, window, cx);
15546    });
15547    cx.executor().run_until_parked();
15548    let second_item_id = workspace
15549        .update(cx, |workspace, window, cx| {
15550            let active_item = workspace
15551                .active_item(cx)
15552                .expect("should have an active item after navigating into the 2nd buffer");
15553            let second_item_id = active_item.item_id();
15554            assert_ne!(
15555                second_item_id, multibuffer_item_id,
15556                "Should navigate away from the multibuffer"
15557            );
15558            assert_ne!(
15559                second_item_id, first_item_id,
15560                "Should navigate into the 2nd buffer and activate it"
15561            );
15562            assert!(
15563                active_item.is_singleton(cx),
15564                "New active item should be a singleton buffer"
15565            );
15566            assert_eq!(
15567                active_item
15568                    .act_as::<Editor>(cx)
15569                    .expect("should have navigated into an editor")
15570                    .read(cx)
15571                    .text(cx),
15572                sample_text_2
15573            );
15574
15575            workspace
15576                .go_back(workspace.active_pane().downgrade(), window, cx)
15577                .detach_and_log_err(cx);
15578
15579            second_item_id
15580        })
15581        .unwrap();
15582    cx.executor().run_until_parked();
15583    workspace
15584        .update(cx, |workspace, _, cx| {
15585            let active_item = workspace
15586                .active_item(cx)
15587                .expect("should have an active item after navigating back from the 2nd buffer");
15588            assert_eq!(
15589                active_item.item_id(),
15590                multibuffer_item_id,
15591                "Should navigate back from the 2nd buffer to the multi buffer"
15592            );
15593            assert!(!active_item.is_singleton(cx));
15594        })
15595        .unwrap();
15596
15597    multi_buffer_editor.update_in(cx, |editor, window, cx| {
15598        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
15599            s.select_ranges(Some(70..70))
15600        });
15601        editor.open_excerpts(&OpenExcerpts, window, cx);
15602    });
15603    cx.executor().run_until_parked();
15604    workspace
15605        .update(cx, |workspace, window, cx| {
15606            let active_item = workspace
15607                .active_item(cx)
15608                .expect("should have an active item after navigating into the 3rd buffer");
15609            let third_item_id = active_item.item_id();
15610            assert_ne!(
15611                third_item_id, multibuffer_item_id,
15612                "Should navigate into the 3rd buffer and activate it"
15613            );
15614            assert_ne!(third_item_id, first_item_id);
15615            assert_ne!(third_item_id, second_item_id);
15616            assert!(
15617                active_item.is_singleton(cx),
15618                "New active item should be a singleton buffer"
15619            );
15620            assert_eq!(
15621                active_item
15622                    .act_as::<Editor>(cx)
15623                    .expect("should have navigated into an editor")
15624                    .read(cx)
15625                    .text(cx),
15626                sample_text_3
15627            );
15628
15629            workspace
15630                .go_back(workspace.active_pane().downgrade(), window, cx)
15631                .detach_and_log_err(cx);
15632        })
15633        .unwrap();
15634    cx.executor().run_until_parked();
15635    workspace
15636        .update(cx, |workspace, _, cx| {
15637            let active_item = workspace
15638                .active_item(cx)
15639                .expect("should have an active item after navigating back from the 3rd buffer");
15640            assert_eq!(
15641                active_item.item_id(),
15642                multibuffer_item_id,
15643                "Should navigate back from the 3rd buffer to the multi buffer"
15644            );
15645            assert!(!active_item.is_singleton(cx));
15646        })
15647        .unwrap();
15648}
15649
15650#[gpui::test]
15651async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15652    init_test(cx, |_| {});
15653
15654    let mut cx = EditorTestContext::new(cx).await;
15655
15656    let diff_base = r#"
15657        use some::mod;
15658
15659        const A: u32 = 42;
15660
15661        fn main() {
15662            println!("hello");
15663
15664            println!("world");
15665        }
15666        "#
15667    .unindent();
15668
15669    cx.set_state(
15670        &r#"
15671        use some::modified;
15672
15673        ˇ
15674        fn main() {
15675            println!("hello there");
15676
15677            println!("around the");
15678            println!("world");
15679        }
15680        "#
15681        .unindent(),
15682    );
15683
15684    cx.set_head_text(&diff_base);
15685    executor.run_until_parked();
15686
15687    cx.update_editor(|editor, window, cx| {
15688        editor.go_to_next_hunk(&GoToHunk, window, cx);
15689        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15690    });
15691    executor.run_until_parked();
15692    cx.assert_state_with_diff(
15693        r#"
15694          use some::modified;
15695
15696
15697          fn main() {
15698        -     println!("hello");
15699        + ˇ    println!("hello there");
15700
15701              println!("around the");
15702              println!("world");
15703          }
15704        "#
15705        .unindent(),
15706    );
15707
15708    cx.update_editor(|editor, window, cx| {
15709        for _ in 0..2 {
15710            editor.go_to_next_hunk(&GoToHunk, window, cx);
15711            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15712        }
15713    });
15714    executor.run_until_parked();
15715    cx.assert_state_with_diff(
15716        r#"
15717        - use some::mod;
15718        + ˇuse some::modified;
15719
15720
15721          fn main() {
15722        -     println!("hello");
15723        +     println!("hello there");
15724
15725        +     println!("around the");
15726              println!("world");
15727          }
15728        "#
15729        .unindent(),
15730    );
15731
15732    cx.update_editor(|editor, window, cx| {
15733        editor.go_to_next_hunk(&GoToHunk, window, cx);
15734        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15735    });
15736    executor.run_until_parked();
15737    cx.assert_state_with_diff(
15738        r#"
15739        - use some::mod;
15740        + use some::modified;
15741
15742        - const A: u32 = 42;
15743          ˇ
15744          fn main() {
15745        -     println!("hello");
15746        +     println!("hello there");
15747
15748        +     println!("around the");
15749              println!("world");
15750          }
15751        "#
15752        .unindent(),
15753    );
15754
15755    cx.update_editor(|editor, window, cx| {
15756        editor.cancel(&Cancel, window, cx);
15757    });
15758
15759    cx.assert_state_with_diff(
15760        r#"
15761          use some::modified;
15762
15763          ˇ
15764          fn main() {
15765              println!("hello there");
15766
15767              println!("around the");
15768              println!("world");
15769          }
15770        "#
15771        .unindent(),
15772    );
15773}
15774
15775#[gpui::test]
15776async fn test_diff_base_change_with_expanded_diff_hunks(
15777    executor: BackgroundExecutor,
15778    cx: &mut TestAppContext,
15779) {
15780    init_test(cx, |_| {});
15781
15782    let mut cx = EditorTestContext::new(cx).await;
15783
15784    let diff_base = r#"
15785        use some::mod1;
15786        use some::mod2;
15787
15788        const A: u32 = 42;
15789        const B: u32 = 42;
15790        const C: u32 = 42;
15791
15792        fn main() {
15793            println!("hello");
15794
15795            println!("world");
15796        }
15797        "#
15798    .unindent();
15799
15800    cx.set_state(
15801        &r#"
15802        use some::mod2;
15803
15804        const A: u32 = 42;
15805        const C: u32 = 42;
15806
15807        fn main(ˇ) {
15808            //println!("hello");
15809
15810            println!("world");
15811            //
15812            //
15813        }
15814        "#
15815        .unindent(),
15816    );
15817
15818    cx.set_head_text(&diff_base);
15819    executor.run_until_parked();
15820
15821    cx.update_editor(|editor, window, cx| {
15822        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15823    });
15824    executor.run_until_parked();
15825    cx.assert_state_with_diff(
15826        r#"
15827        - use some::mod1;
15828          use some::mod2;
15829
15830          const A: u32 = 42;
15831        - const B: u32 = 42;
15832          const C: u32 = 42;
15833
15834          fn main(ˇ) {
15835        -     println!("hello");
15836        +     //println!("hello");
15837
15838              println!("world");
15839        +     //
15840        +     //
15841          }
15842        "#
15843        .unindent(),
15844    );
15845
15846    cx.set_head_text("new diff base!");
15847    executor.run_until_parked();
15848    cx.assert_state_with_diff(
15849        r#"
15850        - new diff base!
15851        + use some::mod2;
15852        +
15853        + const A: u32 = 42;
15854        + const C: u32 = 42;
15855        +
15856        + fn main(ˇ) {
15857        +     //println!("hello");
15858        +
15859        +     println!("world");
15860        +     //
15861        +     //
15862        + }
15863        "#
15864        .unindent(),
15865    );
15866}
15867
15868#[gpui::test]
15869async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
15870    init_test(cx, |_| {});
15871
15872    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15873    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
15874    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15875    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
15876    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
15877    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
15878
15879    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
15880    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
15881    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
15882
15883    let multi_buffer = cx.new(|cx| {
15884        let mut multibuffer = MultiBuffer::new(ReadWrite);
15885        multibuffer.push_excerpts(
15886            buffer_1.clone(),
15887            [
15888                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15889                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15890                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15891            ],
15892            cx,
15893        );
15894        multibuffer.push_excerpts(
15895            buffer_2.clone(),
15896            [
15897                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15898                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15899                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15900            ],
15901            cx,
15902        );
15903        multibuffer.push_excerpts(
15904            buffer_3.clone(),
15905            [
15906                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
15907                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
15908                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
15909            ],
15910            cx,
15911        );
15912        multibuffer
15913    });
15914
15915    let editor =
15916        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
15917    editor
15918        .update(cx, |editor, _window, cx| {
15919            for (buffer, diff_base) in [
15920                (buffer_1.clone(), file_1_old),
15921                (buffer_2.clone(), file_2_old),
15922                (buffer_3.clone(), file_3_old),
15923            ] {
15924                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
15925                editor
15926                    .buffer
15927                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
15928            }
15929        })
15930        .unwrap();
15931
15932    let mut cx = EditorTestContext::for_editor(editor, cx).await;
15933    cx.run_until_parked();
15934
15935    cx.assert_editor_state(
15936        &"
15937            ˇaaa
15938            ccc
15939            ddd
15940
15941            ggg
15942            hhh
15943
15944
15945            lll
15946            mmm
15947            NNN
15948
15949            qqq
15950            rrr
15951
15952            uuu
15953            111
15954            222
15955            333
15956
15957            666
15958            777
15959
15960            000
15961            !!!"
15962        .unindent(),
15963    );
15964
15965    cx.update_editor(|editor, window, cx| {
15966        editor.select_all(&SelectAll, window, cx);
15967        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
15968    });
15969    cx.executor().run_until_parked();
15970
15971    cx.assert_state_with_diff(
15972        "
15973            «aaa
15974          - bbb
15975            ccc
15976            ddd
15977
15978            ggg
15979            hhh
15980
15981
15982            lll
15983            mmm
15984          - nnn
15985          + NNN
15986
15987            qqq
15988            rrr
15989
15990            uuu
15991            111
15992            222
15993            333
15994
15995          + 666
15996            777
15997
15998            000
15999            !!!ˇ»"
16000            .unindent(),
16001    );
16002}
16003
16004#[gpui::test]
16005async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16006    init_test(cx, |_| {});
16007
16008    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16009    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16010
16011    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16012    let multi_buffer = cx.new(|cx| {
16013        let mut multibuffer = MultiBuffer::new(ReadWrite);
16014        multibuffer.push_excerpts(
16015            buffer.clone(),
16016            [
16017                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16018                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16019                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16020            ],
16021            cx,
16022        );
16023        multibuffer
16024    });
16025
16026    let editor =
16027        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16028    editor
16029        .update(cx, |editor, _window, cx| {
16030            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16031            editor
16032                .buffer
16033                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16034        })
16035        .unwrap();
16036
16037    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16038    cx.run_until_parked();
16039
16040    cx.update_editor(|editor, window, cx| {
16041        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16042    });
16043    cx.executor().run_until_parked();
16044
16045    // When the start of a hunk coincides with the start of its excerpt,
16046    // the hunk is expanded. When the start of a a hunk is earlier than
16047    // the start of its excerpt, the hunk is not expanded.
16048    cx.assert_state_with_diff(
16049        "
16050            ˇaaa
16051          - bbb
16052          + BBB
16053
16054          - ddd
16055          - eee
16056          + DDD
16057          + EEE
16058            fff
16059
16060            iii
16061        "
16062        .unindent(),
16063    );
16064}
16065
16066#[gpui::test]
16067async fn test_edits_around_expanded_insertion_hunks(
16068    executor: BackgroundExecutor,
16069    cx: &mut TestAppContext,
16070) {
16071    init_test(cx, |_| {});
16072
16073    let mut cx = EditorTestContext::new(cx).await;
16074
16075    let diff_base = r#"
16076        use some::mod1;
16077        use some::mod2;
16078
16079        const A: u32 = 42;
16080
16081        fn main() {
16082            println!("hello");
16083
16084            println!("world");
16085        }
16086        "#
16087    .unindent();
16088    executor.run_until_parked();
16089    cx.set_state(
16090        &r#"
16091        use some::mod1;
16092        use some::mod2;
16093
16094        const A: u32 = 42;
16095        const B: u32 = 42;
16096        const C: u32 = 42;
16097        ˇ
16098
16099        fn main() {
16100            println!("hello");
16101
16102            println!("world");
16103        }
16104        "#
16105        .unindent(),
16106    );
16107
16108    cx.set_head_text(&diff_base);
16109    executor.run_until_parked();
16110
16111    cx.update_editor(|editor, window, cx| {
16112        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16113    });
16114    executor.run_until_parked();
16115
16116    cx.assert_state_with_diff(
16117        r#"
16118        use some::mod1;
16119        use some::mod2;
16120
16121        const A: u32 = 42;
16122      + const B: u32 = 42;
16123      + const C: u32 = 42;
16124      + ˇ
16125
16126        fn main() {
16127            println!("hello");
16128
16129            println!("world");
16130        }
16131      "#
16132        .unindent(),
16133    );
16134
16135    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16136    executor.run_until_parked();
16137
16138    cx.assert_state_with_diff(
16139        r#"
16140        use some::mod1;
16141        use some::mod2;
16142
16143        const A: u32 = 42;
16144      + const B: u32 = 42;
16145      + const C: u32 = 42;
16146      + const D: u32 = 42;
16147      + ˇ
16148
16149        fn main() {
16150            println!("hello");
16151
16152            println!("world");
16153        }
16154      "#
16155        .unindent(),
16156    );
16157
16158    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16159    executor.run_until_parked();
16160
16161    cx.assert_state_with_diff(
16162        r#"
16163        use some::mod1;
16164        use some::mod2;
16165
16166        const A: u32 = 42;
16167      + const B: u32 = 42;
16168      + const C: u32 = 42;
16169      + const D: u32 = 42;
16170      + const E: u32 = 42;
16171      + ˇ
16172
16173        fn main() {
16174            println!("hello");
16175
16176            println!("world");
16177        }
16178      "#
16179        .unindent(),
16180    );
16181
16182    cx.update_editor(|editor, window, cx| {
16183        editor.delete_line(&DeleteLine, window, cx);
16184    });
16185    executor.run_until_parked();
16186
16187    cx.assert_state_with_diff(
16188        r#"
16189        use some::mod1;
16190        use some::mod2;
16191
16192        const A: u32 = 42;
16193      + const B: u32 = 42;
16194      + const C: u32 = 42;
16195      + const D: u32 = 42;
16196      + const E: u32 = 42;
16197        ˇ
16198        fn main() {
16199            println!("hello");
16200
16201            println!("world");
16202        }
16203      "#
16204        .unindent(),
16205    );
16206
16207    cx.update_editor(|editor, window, cx| {
16208        editor.move_up(&MoveUp, window, cx);
16209        editor.delete_line(&DeleteLine, window, cx);
16210        editor.move_up(&MoveUp, window, cx);
16211        editor.delete_line(&DeleteLine, window, cx);
16212        editor.move_up(&MoveUp, window, cx);
16213        editor.delete_line(&DeleteLine, window, cx);
16214    });
16215    executor.run_until_parked();
16216    cx.assert_state_with_diff(
16217        r#"
16218        use some::mod1;
16219        use some::mod2;
16220
16221        const A: u32 = 42;
16222      + const B: u32 = 42;
16223        ˇ
16224        fn main() {
16225            println!("hello");
16226
16227            println!("world");
16228        }
16229      "#
16230        .unindent(),
16231    );
16232
16233    cx.update_editor(|editor, window, cx| {
16234        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16235        editor.delete_line(&DeleteLine, window, cx);
16236    });
16237    executor.run_until_parked();
16238    cx.assert_state_with_diff(
16239        r#"
16240        ˇ
16241        fn main() {
16242            println!("hello");
16243
16244            println!("world");
16245        }
16246      "#
16247        .unindent(),
16248    );
16249}
16250
16251#[gpui::test]
16252async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
16253    init_test(cx, |_| {});
16254
16255    let mut cx = EditorTestContext::new(cx).await;
16256    cx.set_head_text(indoc! { "
16257        one
16258        two
16259        three
16260        four
16261        five
16262        "
16263    });
16264    cx.set_state(indoc! { "
16265        one
16266        ˇthree
16267        five
16268    "});
16269    cx.run_until_parked();
16270    cx.update_editor(|editor, window, cx| {
16271        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16272    });
16273    cx.assert_state_with_diff(
16274        indoc! { "
16275        one
16276      - two
16277        ˇthree
16278      - four
16279        five
16280    "}
16281        .to_string(),
16282    );
16283    cx.update_editor(|editor, window, cx| {
16284        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16285    });
16286
16287    cx.assert_state_with_diff(
16288        indoc! { "
16289        one
16290        ˇthree
16291        five
16292    "}
16293        .to_string(),
16294    );
16295
16296    cx.set_state(indoc! { "
16297        one
16298        ˇTWO
16299        three
16300        four
16301        five
16302    "});
16303    cx.run_until_parked();
16304    cx.update_editor(|editor, window, cx| {
16305        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16306    });
16307
16308    cx.assert_state_with_diff(
16309        indoc! { "
16310            one
16311          - two
16312          + ˇTWO
16313            three
16314            four
16315            five
16316        "}
16317        .to_string(),
16318    );
16319    cx.update_editor(|editor, window, cx| {
16320        editor.move_up(&Default::default(), window, cx);
16321        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
16322    });
16323    cx.assert_state_with_diff(
16324        indoc! { "
16325            one
16326            ˇTWO
16327            three
16328            four
16329            five
16330        "}
16331        .to_string(),
16332    );
16333}
16334
16335#[gpui::test]
16336async fn test_edits_around_expanded_deletion_hunks(
16337    executor: BackgroundExecutor,
16338    cx: &mut TestAppContext,
16339) {
16340    init_test(cx, |_| {});
16341
16342    let mut cx = EditorTestContext::new(cx).await;
16343
16344    let diff_base = r#"
16345        use some::mod1;
16346        use some::mod2;
16347
16348        const A: u32 = 42;
16349        const B: u32 = 42;
16350        const C: u32 = 42;
16351
16352
16353        fn main() {
16354            println!("hello");
16355
16356            println!("world");
16357        }
16358    "#
16359    .unindent();
16360    executor.run_until_parked();
16361    cx.set_state(
16362        &r#"
16363        use some::mod1;
16364        use some::mod2;
16365
16366        ˇconst B: u32 = 42;
16367        const C: u32 = 42;
16368
16369
16370        fn main() {
16371            println!("hello");
16372
16373            println!("world");
16374        }
16375        "#
16376        .unindent(),
16377    );
16378
16379    cx.set_head_text(&diff_base);
16380    executor.run_until_parked();
16381
16382    cx.update_editor(|editor, window, cx| {
16383        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16384    });
16385    executor.run_until_parked();
16386
16387    cx.assert_state_with_diff(
16388        r#"
16389        use some::mod1;
16390        use some::mod2;
16391
16392      - const A: u32 = 42;
16393        ˇconst B: u32 = 42;
16394        const C: u32 = 42;
16395
16396
16397        fn main() {
16398            println!("hello");
16399
16400            println!("world");
16401        }
16402      "#
16403        .unindent(),
16404    );
16405
16406    cx.update_editor(|editor, window, cx| {
16407        editor.delete_line(&DeleteLine, window, cx);
16408    });
16409    executor.run_until_parked();
16410    cx.assert_state_with_diff(
16411        r#"
16412        use some::mod1;
16413        use some::mod2;
16414
16415      - const A: u32 = 42;
16416      - const B: u32 = 42;
16417        ˇconst C: u32 = 42;
16418
16419
16420        fn main() {
16421            println!("hello");
16422
16423            println!("world");
16424        }
16425      "#
16426        .unindent(),
16427    );
16428
16429    cx.update_editor(|editor, window, cx| {
16430        editor.delete_line(&DeleteLine, window, cx);
16431    });
16432    executor.run_until_parked();
16433    cx.assert_state_with_diff(
16434        r#"
16435        use some::mod1;
16436        use some::mod2;
16437
16438      - const A: u32 = 42;
16439      - const B: u32 = 42;
16440      - const C: u32 = 42;
16441        ˇ
16442
16443        fn main() {
16444            println!("hello");
16445
16446            println!("world");
16447        }
16448      "#
16449        .unindent(),
16450    );
16451
16452    cx.update_editor(|editor, window, cx| {
16453        editor.handle_input("replacement", window, cx);
16454    });
16455    executor.run_until_parked();
16456    cx.assert_state_with_diff(
16457        r#"
16458        use some::mod1;
16459        use some::mod2;
16460
16461      - const A: u32 = 42;
16462      - const B: u32 = 42;
16463      - const C: u32 = 42;
16464      -
16465      + replacementˇ
16466
16467        fn main() {
16468            println!("hello");
16469
16470            println!("world");
16471        }
16472      "#
16473        .unindent(),
16474    );
16475}
16476
16477#[gpui::test]
16478async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16479    init_test(cx, |_| {});
16480
16481    let mut cx = EditorTestContext::new(cx).await;
16482
16483    let base_text = r#"
16484        one
16485        two
16486        three
16487        four
16488        five
16489    "#
16490    .unindent();
16491    executor.run_until_parked();
16492    cx.set_state(
16493        &r#"
16494        one
16495        two
16496        fˇour
16497        five
16498        "#
16499        .unindent(),
16500    );
16501
16502    cx.set_head_text(&base_text);
16503    executor.run_until_parked();
16504
16505    cx.update_editor(|editor, window, cx| {
16506        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16507    });
16508    executor.run_until_parked();
16509
16510    cx.assert_state_with_diff(
16511        r#"
16512          one
16513          two
16514        - three
16515          fˇour
16516          five
16517        "#
16518        .unindent(),
16519    );
16520
16521    cx.update_editor(|editor, window, cx| {
16522        editor.backspace(&Backspace, window, cx);
16523        editor.backspace(&Backspace, window, cx);
16524    });
16525    executor.run_until_parked();
16526    cx.assert_state_with_diff(
16527        r#"
16528          one
16529          two
16530        - threeˇ
16531        - four
16532        + our
16533          five
16534        "#
16535        .unindent(),
16536    );
16537}
16538
16539#[gpui::test]
16540async fn test_edit_after_expanded_modification_hunk(
16541    executor: BackgroundExecutor,
16542    cx: &mut TestAppContext,
16543) {
16544    init_test(cx, |_| {});
16545
16546    let mut cx = EditorTestContext::new(cx).await;
16547
16548    let diff_base = r#"
16549        use some::mod1;
16550        use some::mod2;
16551
16552        const A: u32 = 42;
16553        const B: u32 = 42;
16554        const C: u32 = 42;
16555        const D: u32 = 42;
16556
16557
16558        fn main() {
16559            println!("hello");
16560
16561            println!("world");
16562        }"#
16563    .unindent();
16564
16565    cx.set_state(
16566        &r#"
16567        use some::mod1;
16568        use some::mod2;
16569
16570        const A: u32 = 42;
16571        const B: u32 = 42;
16572        const C: u32 = 43ˇ
16573        const D: u32 = 42;
16574
16575
16576        fn main() {
16577            println!("hello");
16578
16579            println!("world");
16580        }"#
16581        .unindent(),
16582    );
16583
16584    cx.set_head_text(&diff_base);
16585    executor.run_until_parked();
16586    cx.update_editor(|editor, window, cx| {
16587        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16588    });
16589    executor.run_until_parked();
16590
16591    cx.assert_state_with_diff(
16592        r#"
16593        use some::mod1;
16594        use some::mod2;
16595
16596        const A: u32 = 42;
16597        const B: u32 = 42;
16598      - const C: u32 = 42;
16599      + const C: u32 = 43ˇ
16600        const D: u32 = 42;
16601
16602
16603        fn main() {
16604            println!("hello");
16605
16606            println!("world");
16607        }"#
16608        .unindent(),
16609    );
16610
16611    cx.update_editor(|editor, window, cx| {
16612        editor.handle_input("\nnew_line\n", window, cx);
16613    });
16614    executor.run_until_parked();
16615
16616    cx.assert_state_with_diff(
16617        r#"
16618        use some::mod1;
16619        use some::mod2;
16620
16621        const A: u32 = 42;
16622        const B: u32 = 42;
16623      - const C: u32 = 42;
16624      + const C: u32 = 43
16625      + new_line
16626      + ˇ
16627        const D: u32 = 42;
16628
16629
16630        fn main() {
16631            println!("hello");
16632
16633            println!("world");
16634        }"#
16635        .unindent(),
16636    );
16637}
16638
16639#[gpui::test]
16640async fn test_stage_and_unstage_added_file_hunk(
16641    executor: BackgroundExecutor,
16642    cx: &mut TestAppContext,
16643) {
16644    init_test(cx, |_| {});
16645
16646    let mut cx = EditorTestContext::new(cx).await;
16647    cx.update_editor(|editor, _, cx| {
16648        editor.set_expand_all_diff_hunks(cx);
16649    });
16650
16651    let working_copy = r#"
16652            ˇfn main() {
16653                println!("hello, world!");
16654            }
16655        "#
16656    .unindent();
16657
16658    cx.set_state(&working_copy);
16659    executor.run_until_parked();
16660
16661    cx.assert_state_with_diff(
16662        r#"
16663            + ˇfn main() {
16664            +     println!("hello, world!");
16665            + }
16666        "#
16667        .unindent(),
16668    );
16669    cx.assert_index_text(None);
16670
16671    cx.update_editor(|editor, window, cx| {
16672        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16673    });
16674    executor.run_until_parked();
16675    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
16676    cx.assert_state_with_diff(
16677        r#"
16678            + ˇfn main() {
16679            +     println!("hello, world!");
16680            + }
16681        "#
16682        .unindent(),
16683    );
16684
16685    cx.update_editor(|editor, window, cx| {
16686        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16687    });
16688    executor.run_until_parked();
16689    cx.assert_index_text(None);
16690}
16691
16692async fn setup_indent_guides_editor(
16693    text: &str,
16694    cx: &mut TestAppContext,
16695) -> (BufferId, EditorTestContext) {
16696    init_test(cx, |_| {});
16697
16698    let mut cx = EditorTestContext::new(cx).await;
16699
16700    let buffer_id = cx.update_editor(|editor, window, cx| {
16701        editor.set_text(text, window, cx);
16702        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
16703
16704        buffer_ids[0]
16705    });
16706
16707    (buffer_id, cx)
16708}
16709
16710fn assert_indent_guides(
16711    range: Range<u32>,
16712    expected: Vec<IndentGuide>,
16713    active_indices: Option<Vec<usize>>,
16714    cx: &mut EditorTestContext,
16715) {
16716    let indent_guides = cx.update_editor(|editor, window, cx| {
16717        let snapshot = editor.snapshot(window, cx).display_snapshot;
16718        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
16719            editor,
16720            MultiBufferRow(range.start)..MultiBufferRow(range.end),
16721            true,
16722            &snapshot,
16723            cx,
16724        );
16725
16726        indent_guides.sort_by(|a, b| {
16727            a.depth.cmp(&b.depth).then(
16728                a.start_row
16729                    .cmp(&b.start_row)
16730                    .then(a.end_row.cmp(&b.end_row)),
16731            )
16732        });
16733        indent_guides
16734    });
16735
16736    if let Some(expected) = active_indices {
16737        let active_indices = cx.update_editor(|editor, window, cx| {
16738            let snapshot = editor.snapshot(window, cx).display_snapshot;
16739            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
16740        });
16741
16742        assert_eq!(
16743            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
16744            expected,
16745            "Active indent guide indices do not match"
16746        );
16747    }
16748
16749    assert_eq!(indent_guides, expected, "Indent guides do not match");
16750}
16751
16752fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
16753    IndentGuide {
16754        buffer_id,
16755        start_row: MultiBufferRow(start_row),
16756        end_row: MultiBufferRow(end_row),
16757        depth,
16758        tab_size: 4,
16759        settings: IndentGuideSettings {
16760            enabled: true,
16761            line_width: 1,
16762            active_line_width: 1,
16763            ..Default::default()
16764        },
16765    }
16766}
16767
16768#[gpui::test]
16769async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
16770    let (buffer_id, mut cx) = setup_indent_guides_editor(
16771        &"
16772    fn main() {
16773        let a = 1;
16774    }"
16775        .unindent(),
16776        cx,
16777    )
16778    .await;
16779
16780    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
16781}
16782
16783#[gpui::test]
16784async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
16785    let (buffer_id, mut cx) = setup_indent_guides_editor(
16786        &"
16787    fn main() {
16788        let a = 1;
16789        let b = 2;
16790    }"
16791        .unindent(),
16792        cx,
16793    )
16794    .await;
16795
16796    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
16797}
16798
16799#[gpui::test]
16800async fn test_indent_guide_nested(cx: &mut TestAppContext) {
16801    let (buffer_id, mut cx) = setup_indent_guides_editor(
16802        &"
16803    fn main() {
16804        let a = 1;
16805        if a == 3 {
16806            let b = 2;
16807        } else {
16808            let c = 3;
16809        }
16810    }"
16811        .unindent(),
16812        cx,
16813    )
16814    .await;
16815
16816    assert_indent_guides(
16817        0..8,
16818        vec![
16819            indent_guide(buffer_id, 1, 6, 0),
16820            indent_guide(buffer_id, 3, 3, 1),
16821            indent_guide(buffer_id, 5, 5, 1),
16822        ],
16823        None,
16824        &mut cx,
16825    );
16826}
16827
16828#[gpui::test]
16829async fn test_indent_guide_tab(cx: &mut TestAppContext) {
16830    let (buffer_id, mut cx) = setup_indent_guides_editor(
16831        &"
16832    fn main() {
16833        let a = 1;
16834            let b = 2;
16835        let c = 3;
16836    }"
16837        .unindent(),
16838        cx,
16839    )
16840    .await;
16841
16842    assert_indent_guides(
16843        0..5,
16844        vec![
16845            indent_guide(buffer_id, 1, 3, 0),
16846            indent_guide(buffer_id, 2, 2, 1),
16847        ],
16848        None,
16849        &mut cx,
16850    );
16851}
16852
16853#[gpui::test]
16854async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
16855    let (buffer_id, mut cx) = setup_indent_guides_editor(
16856        &"
16857        fn main() {
16858            let a = 1;
16859
16860            let c = 3;
16861        }"
16862        .unindent(),
16863        cx,
16864    )
16865    .await;
16866
16867    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
16868}
16869
16870#[gpui::test]
16871async fn test_indent_guide_complex(cx: &mut TestAppContext) {
16872    let (buffer_id, mut cx) = setup_indent_guides_editor(
16873        &"
16874        fn main() {
16875            let a = 1;
16876
16877            let c = 3;
16878
16879            if a == 3 {
16880                let b = 2;
16881            } else {
16882                let c = 3;
16883            }
16884        }"
16885        .unindent(),
16886        cx,
16887    )
16888    .await;
16889
16890    assert_indent_guides(
16891        0..11,
16892        vec![
16893            indent_guide(buffer_id, 1, 9, 0),
16894            indent_guide(buffer_id, 6, 6, 1),
16895            indent_guide(buffer_id, 8, 8, 1),
16896        ],
16897        None,
16898        &mut cx,
16899    );
16900}
16901
16902#[gpui::test]
16903async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
16904    let (buffer_id, mut cx) = setup_indent_guides_editor(
16905        &"
16906        fn main() {
16907            let a = 1;
16908
16909            let c = 3;
16910
16911            if a == 3 {
16912                let b = 2;
16913            } else {
16914                let c = 3;
16915            }
16916        }"
16917        .unindent(),
16918        cx,
16919    )
16920    .await;
16921
16922    assert_indent_guides(
16923        1..11,
16924        vec![
16925            indent_guide(buffer_id, 1, 9, 0),
16926            indent_guide(buffer_id, 6, 6, 1),
16927            indent_guide(buffer_id, 8, 8, 1),
16928        ],
16929        None,
16930        &mut cx,
16931    );
16932}
16933
16934#[gpui::test]
16935async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
16936    let (buffer_id, mut cx) = setup_indent_guides_editor(
16937        &"
16938        fn main() {
16939            let a = 1;
16940
16941            let c = 3;
16942
16943            if a == 3 {
16944                let b = 2;
16945            } else {
16946                let c = 3;
16947            }
16948        }"
16949        .unindent(),
16950        cx,
16951    )
16952    .await;
16953
16954    assert_indent_guides(
16955        1..10,
16956        vec![
16957            indent_guide(buffer_id, 1, 9, 0),
16958            indent_guide(buffer_id, 6, 6, 1),
16959            indent_guide(buffer_id, 8, 8, 1),
16960        ],
16961        None,
16962        &mut cx,
16963    );
16964}
16965
16966#[gpui::test]
16967async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
16968    let (buffer_id, mut cx) = setup_indent_guides_editor(
16969        &"
16970        block1
16971            block2
16972                block3
16973                    block4
16974            block2
16975        block1
16976        block1"
16977            .unindent(),
16978        cx,
16979    )
16980    .await;
16981
16982    assert_indent_guides(
16983        1..10,
16984        vec![
16985            indent_guide(buffer_id, 1, 4, 0),
16986            indent_guide(buffer_id, 2, 3, 1),
16987            indent_guide(buffer_id, 3, 3, 2),
16988        ],
16989        None,
16990        &mut cx,
16991    );
16992}
16993
16994#[gpui::test]
16995async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
16996    let (buffer_id, mut cx) = setup_indent_guides_editor(
16997        &"
16998        block1
16999            block2
17000                block3
17001
17002        block1
17003        block1"
17004            .unindent(),
17005        cx,
17006    )
17007    .await;
17008
17009    assert_indent_guides(
17010        0..6,
17011        vec![
17012            indent_guide(buffer_id, 1, 2, 0),
17013            indent_guide(buffer_id, 2, 2, 1),
17014        ],
17015        None,
17016        &mut cx,
17017    );
17018}
17019
17020#[gpui::test]
17021async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17022    let (buffer_id, mut cx) = setup_indent_guides_editor(
17023        &"
17024        block1
17025
17026
17027
17028            block2
17029        "
17030        .unindent(),
17031        cx,
17032    )
17033    .await;
17034
17035    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17036}
17037
17038#[gpui::test]
17039async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17040    let (buffer_id, mut cx) = setup_indent_guides_editor(
17041        &"
17042        def a:
17043        \tb = 3
17044        \tif True:
17045        \t\tc = 4
17046        \t\td = 5
17047        \tprint(b)
17048        "
17049        .unindent(),
17050        cx,
17051    )
17052    .await;
17053
17054    assert_indent_guides(
17055        0..6,
17056        vec![
17057            indent_guide(buffer_id, 1, 5, 0),
17058            indent_guide(buffer_id, 3, 4, 1),
17059        ],
17060        None,
17061        &mut cx,
17062    );
17063}
17064
17065#[gpui::test]
17066async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17067    let (buffer_id, mut cx) = setup_indent_guides_editor(
17068        &"
17069    fn main() {
17070        let a = 1;
17071    }"
17072        .unindent(),
17073        cx,
17074    )
17075    .await;
17076
17077    cx.update_editor(|editor, window, cx| {
17078        editor.change_selections(None, window, cx, |s| {
17079            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17080        });
17081    });
17082
17083    assert_indent_guides(
17084        0..3,
17085        vec![indent_guide(buffer_id, 1, 1, 0)],
17086        Some(vec![0]),
17087        &mut cx,
17088    );
17089}
17090
17091#[gpui::test]
17092async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17093    let (buffer_id, mut cx) = setup_indent_guides_editor(
17094        &"
17095    fn main() {
17096        if 1 == 2 {
17097            let a = 1;
17098        }
17099    }"
17100        .unindent(),
17101        cx,
17102    )
17103    .await;
17104
17105    cx.update_editor(|editor, window, cx| {
17106        editor.change_selections(None, window, cx, |s| {
17107            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17108        });
17109    });
17110
17111    assert_indent_guides(
17112        0..4,
17113        vec![
17114            indent_guide(buffer_id, 1, 3, 0),
17115            indent_guide(buffer_id, 2, 2, 1),
17116        ],
17117        Some(vec![1]),
17118        &mut cx,
17119    );
17120
17121    cx.update_editor(|editor, window, cx| {
17122        editor.change_selections(None, window, cx, |s| {
17123            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17124        });
17125    });
17126
17127    assert_indent_guides(
17128        0..4,
17129        vec![
17130            indent_guide(buffer_id, 1, 3, 0),
17131            indent_guide(buffer_id, 2, 2, 1),
17132        ],
17133        Some(vec![1]),
17134        &mut cx,
17135    );
17136
17137    cx.update_editor(|editor, window, cx| {
17138        editor.change_selections(None, window, cx, |s| {
17139            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
17140        });
17141    });
17142
17143    assert_indent_guides(
17144        0..4,
17145        vec![
17146            indent_guide(buffer_id, 1, 3, 0),
17147            indent_guide(buffer_id, 2, 2, 1),
17148        ],
17149        Some(vec![0]),
17150        &mut cx,
17151    );
17152}
17153
17154#[gpui::test]
17155async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
17156    let (buffer_id, mut cx) = setup_indent_guides_editor(
17157        &"
17158    fn main() {
17159        let a = 1;
17160
17161        let b = 2;
17162    }"
17163        .unindent(),
17164        cx,
17165    )
17166    .await;
17167
17168    cx.update_editor(|editor, window, cx| {
17169        editor.change_selections(None, window, cx, |s| {
17170            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
17171        });
17172    });
17173
17174    assert_indent_guides(
17175        0..5,
17176        vec![indent_guide(buffer_id, 1, 3, 0)],
17177        Some(vec![0]),
17178        &mut cx,
17179    );
17180}
17181
17182#[gpui::test]
17183async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
17184    let (buffer_id, mut cx) = setup_indent_guides_editor(
17185        &"
17186    def m:
17187        a = 1
17188        pass"
17189            .unindent(),
17190        cx,
17191    )
17192    .await;
17193
17194    cx.update_editor(|editor, window, cx| {
17195        editor.change_selections(None, window, cx, |s| {
17196            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17197        });
17198    });
17199
17200    assert_indent_guides(
17201        0..3,
17202        vec![indent_guide(buffer_id, 1, 2, 0)],
17203        Some(vec![0]),
17204        &mut cx,
17205    );
17206}
17207
17208#[gpui::test]
17209async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
17210    init_test(cx, |_| {});
17211    let mut cx = EditorTestContext::new(cx).await;
17212    let text = indoc! {
17213        "
17214        impl A {
17215            fn b() {
17216                0;
17217                3;
17218                5;
17219                6;
17220                7;
17221            }
17222        }
17223        "
17224    };
17225    let base_text = indoc! {
17226        "
17227        impl A {
17228            fn b() {
17229                0;
17230                1;
17231                2;
17232                3;
17233                4;
17234            }
17235            fn c() {
17236                5;
17237                6;
17238                7;
17239            }
17240        }
17241        "
17242    };
17243
17244    cx.update_editor(|editor, window, cx| {
17245        editor.set_text(text, window, cx);
17246
17247        editor.buffer().update(cx, |multibuffer, cx| {
17248            let buffer = multibuffer.as_singleton().unwrap();
17249            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
17250
17251            multibuffer.set_all_diff_hunks_expanded(cx);
17252            multibuffer.add_diff(diff, cx);
17253
17254            buffer.read(cx).remote_id()
17255        })
17256    });
17257    cx.run_until_parked();
17258
17259    cx.assert_state_with_diff(
17260        indoc! { "
17261          impl A {
17262              fn b() {
17263                  0;
17264        -         1;
17265        -         2;
17266                  3;
17267        -         4;
17268        -     }
17269        -     fn c() {
17270                  5;
17271                  6;
17272                  7;
17273              }
17274          }
17275          ˇ"
17276        }
17277        .to_string(),
17278    );
17279
17280    let mut actual_guides = cx.update_editor(|editor, window, cx| {
17281        editor
17282            .snapshot(window, cx)
17283            .buffer_snapshot
17284            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
17285            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
17286            .collect::<Vec<_>>()
17287    });
17288    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
17289    assert_eq!(
17290        actual_guides,
17291        vec![
17292            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
17293            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
17294            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
17295        ]
17296    );
17297}
17298
17299#[gpui::test]
17300async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17301    init_test(cx, |_| {});
17302    let mut cx = EditorTestContext::new(cx).await;
17303
17304    let diff_base = r#"
17305        a
17306        b
17307        c
17308        "#
17309    .unindent();
17310
17311    cx.set_state(
17312        &r#"
17313        ˇA
17314        b
17315        C
17316        "#
17317        .unindent(),
17318    );
17319    cx.set_head_text(&diff_base);
17320    cx.update_editor(|editor, window, cx| {
17321        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17322    });
17323    executor.run_until_parked();
17324
17325    let both_hunks_expanded = r#"
17326        - a
17327        + ˇA
17328          b
17329        - c
17330        + C
17331        "#
17332    .unindent();
17333
17334    cx.assert_state_with_diff(both_hunks_expanded.clone());
17335
17336    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17337        let snapshot = editor.snapshot(window, cx);
17338        let hunks = editor
17339            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17340            .collect::<Vec<_>>();
17341        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17342        let buffer_id = hunks[0].buffer_id;
17343        hunks
17344            .into_iter()
17345            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17346            .collect::<Vec<_>>()
17347    });
17348    assert_eq!(hunk_ranges.len(), 2);
17349
17350    cx.update_editor(|editor, _, cx| {
17351        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17352    });
17353    executor.run_until_parked();
17354
17355    let second_hunk_expanded = r#"
17356          ˇA
17357          b
17358        - c
17359        + C
17360        "#
17361    .unindent();
17362
17363    cx.assert_state_with_diff(second_hunk_expanded);
17364
17365    cx.update_editor(|editor, _, cx| {
17366        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17367    });
17368    executor.run_until_parked();
17369
17370    cx.assert_state_with_diff(both_hunks_expanded.clone());
17371
17372    cx.update_editor(|editor, _, cx| {
17373        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17374    });
17375    executor.run_until_parked();
17376
17377    let first_hunk_expanded = r#"
17378        - a
17379        + ˇA
17380          b
17381          C
17382        "#
17383    .unindent();
17384
17385    cx.assert_state_with_diff(first_hunk_expanded);
17386
17387    cx.update_editor(|editor, _, cx| {
17388        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17389    });
17390    executor.run_until_parked();
17391
17392    cx.assert_state_with_diff(both_hunks_expanded);
17393
17394    cx.set_state(
17395        &r#"
17396        ˇA
17397        b
17398        "#
17399        .unindent(),
17400    );
17401    cx.run_until_parked();
17402
17403    // TODO this cursor position seems bad
17404    cx.assert_state_with_diff(
17405        r#"
17406        - ˇa
17407        + A
17408          b
17409        "#
17410        .unindent(),
17411    );
17412
17413    cx.update_editor(|editor, window, cx| {
17414        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17415    });
17416
17417    cx.assert_state_with_diff(
17418        r#"
17419            - ˇa
17420            + A
17421              b
17422            - c
17423            "#
17424        .unindent(),
17425    );
17426
17427    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17428        let snapshot = editor.snapshot(window, cx);
17429        let hunks = editor
17430            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17431            .collect::<Vec<_>>();
17432        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17433        let buffer_id = hunks[0].buffer_id;
17434        hunks
17435            .into_iter()
17436            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17437            .collect::<Vec<_>>()
17438    });
17439    assert_eq!(hunk_ranges.len(), 2);
17440
17441    cx.update_editor(|editor, _, cx| {
17442        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
17443    });
17444    executor.run_until_parked();
17445
17446    cx.assert_state_with_diff(
17447        r#"
17448        - ˇa
17449        + A
17450          b
17451        "#
17452        .unindent(),
17453    );
17454}
17455
17456#[gpui::test]
17457async fn test_toggle_deletion_hunk_at_start_of_file(
17458    executor: BackgroundExecutor,
17459    cx: &mut TestAppContext,
17460) {
17461    init_test(cx, |_| {});
17462    let mut cx = EditorTestContext::new(cx).await;
17463
17464    let diff_base = r#"
17465        a
17466        b
17467        c
17468        "#
17469    .unindent();
17470
17471    cx.set_state(
17472        &r#"
17473        ˇb
17474        c
17475        "#
17476        .unindent(),
17477    );
17478    cx.set_head_text(&diff_base);
17479    cx.update_editor(|editor, window, cx| {
17480        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17481    });
17482    executor.run_until_parked();
17483
17484    let hunk_expanded = r#"
17485        - a
17486          ˇb
17487          c
17488        "#
17489    .unindent();
17490
17491    cx.assert_state_with_diff(hunk_expanded.clone());
17492
17493    let hunk_ranges = cx.update_editor(|editor, window, cx| {
17494        let snapshot = editor.snapshot(window, cx);
17495        let hunks = editor
17496            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17497            .collect::<Vec<_>>();
17498        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
17499        let buffer_id = hunks[0].buffer_id;
17500        hunks
17501            .into_iter()
17502            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
17503            .collect::<Vec<_>>()
17504    });
17505    assert_eq!(hunk_ranges.len(), 1);
17506
17507    cx.update_editor(|editor, _, cx| {
17508        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17509    });
17510    executor.run_until_parked();
17511
17512    let hunk_collapsed = r#"
17513          ˇb
17514          c
17515        "#
17516    .unindent();
17517
17518    cx.assert_state_with_diff(hunk_collapsed);
17519
17520    cx.update_editor(|editor, _, cx| {
17521        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
17522    });
17523    executor.run_until_parked();
17524
17525    cx.assert_state_with_diff(hunk_expanded.clone());
17526}
17527
17528#[gpui::test]
17529async fn test_display_diff_hunks(cx: &mut TestAppContext) {
17530    init_test(cx, |_| {});
17531
17532    let fs = FakeFs::new(cx.executor());
17533    fs.insert_tree(
17534        path!("/test"),
17535        json!({
17536            ".git": {},
17537            "file-1": "ONE\n",
17538            "file-2": "TWO\n",
17539            "file-3": "THREE\n",
17540        }),
17541    )
17542    .await;
17543
17544    fs.set_head_for_repo(
17545        path!("/test/.git").as_ref(),
17546        &[
17547            ("file-1".into(), "one\n".into()),
17548            ("file-2".into(), "two\n".into()),
17549            ("file-3".into(), "three\n".into()),
17550        ],
17551    );
17552
17553    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
17554    let mut buffers = vec![];
17555    for i in 1..=3 {
17556        let buffer = project
17557            .update(cx, |project, cx| {
17558                let path = format!(path!("/test/file-{}"), i);
17559                project.open_local_buffer(path, cx)
17560            })
17561            .await
17562            .unwrap();
17563        buffers.push(buffer);
17564    }
17565
17566    let multibuffer = cx.new(|cx| {
17567        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
17568        multibuffer.set_all_diff_hunks_expanded(cx);
17569        for buffer in &buffers {
17570            let snapshot = buffer.read(cx).snapshot();
17571            multibuffer.set_excerpts_for_path(
17572                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
17573                buffer.clone(),
17574                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
17575                DEFAULT_MULTIBUFFER_CONTEXT,
17576                cx,
17577            );
17578        }
17579        multibuffer
17580    });
17581
17582    let editor = cx.add_window(|window, cx| {
17583        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
17584    });
17585    cx.run_until_parked();
17586
17587    let snapshot = editor
17588        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17589        .unwrap();
17590    let hunks = snapshot
17591        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
17592        .map(|hunk| match hunk {
17593            DisplayDiffHunk::Unfolded {
17594                display_row_range, ..
17595            } => display_row_range,
17596            DisplayDiffHunk::Folded { .. } => unreachable!(),
17597        })
17598        .collect::<Vec<_>>();
17599    assert_eq!(
17600        hunks,
17601        [
17602            DisplayRow(2)..DisplayRow(4),
17603            DisplayRow(7)..DisplayRow(9),
17604            DisplayRow(12)..DisplayRow(14),
17605        ]
17606    );
17607}
17608
17609#[gpui::test]
17610async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
17611    init_test(cx, |_| {});
17612
17613    let mut cx = EditorTestContext::new(cx).await;
17614    cx.set_head_text(indoc! { "
17615        one
17616        two
17617        three
17618        four
17619        five
17620        "
17621    });
17622    cx.set_index_text(indoc! { "
17623        one
17624        two
17625        three
17626        four
17627        five
17628        "
17629    });
17630    cx.set_state(indoc! {"
17631        one
17632        TWO
17633        ˇTHREE
17634        FOUR
17635        five
17636    "});
17637    cx.run_until_parked();
17638    cx.update_editor(|editor, window, cx| {
17639        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17640    });
17641    cx.run_until_parked();
17642    cx.assert_index_text(Some(indoc! {"
17643        one
17644        TWO
17645        THREE
17646        FOUR
17647        five
17648    "}));
17649    cx.set_state(indoc! { "
17650        one
17651        TWO
17652        ˇTHREE-HUNDRED
17653        FOUR
17654        five
17655    "});
17656    cx.run_until_parked();
17657    cx.update_editor(|editor, window, cx| {
17658        let snapshot = editor.snapshot(window, cx);
17659        let hunks = editor
17660            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
17661            .collect::<Vec<_>>();
17662        assert_eq!(hunks.len(), 1);
17663        assert_eq!(
17664            hunks[0].status(),
17665            DiffHunkStatus {
17666                kind: DiffHunkStatusKind::Modified,
17667                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
17668            }
17669        );
17670
17671        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17672    });
17673    cx.run_until_parked();
17674    cx.assert_index_text(Some(indoc! {"
17675        one
17676        TWO
17677        THREE-HUNDRED
17678        FOUR
17679        five
17680    "}));
17681}
17682
17683#[gpui::test]
17684fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
17685    init_test(cx, |_| {});
17686
17687    let editor = cx.add_window(|window, cx| {
17688        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
17689        build_editor(buffer, window, cx)
17690    });
17691
17692    let render_args = Arc::new(Mutex::new(None));
17693    let snapshot = editor
17694        .update(cx, |editor, window, cx| {
17695            let snapshot = editor.buffer().read(cx).snapshot(cx);
17696            let range =
17697                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
17698
17699            struct RenderArgs {
17700                row: MultiBufferRow,
17701                folded: bool,
17702                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
17703            }
17704
17705            let crease = Crease::inline(
17706                range,
17707                FoldPlaceholder::test(),
17708                {
17709                    let toggle_callback = render_args.clone();
17710                    move |row, folded, callback, _window, _cx| {
17711                        *toggle_callback.lock() = Some(RenderArgs {
17712                            row,
17713                            folded,
17714                            callback,
17715                        });
17716                        div()
17717                    }
17718                },
17719                |_row, _folded, _window, _cx| div(),
17720            );
17721
17722            editor.insert_creases(Some(crease), cx);
17723            let snapshot = editor.snapshot(window, cx);
17724            let _div = snapshot.render_crease_toggle(
17725                MultiBufferRow(1),
17726                false,
17727                cx.entity().clone(),
17728                window,
17729                cx,
17730            );
17731            snapshot
17732        })
17733        .unwrap();
17734
17735    let render_args = render_args.lock().take().unwrap();
17736    assert_eq!(render_args.row, MultiBufferRow(1));
17737    assert!(!render_args.folded);
17738    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17739
17740    cx.update_window(*editor, |_, window, cx| {
17741        (render_args.callback)(true, window, cx)
17742    })
17743    .unwrap();
17744    let snapshot = editor
17745        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17746        .unwrap();
17747    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
17748
17749    cx.update_window(*editor, |_, window, cx| {
17750        (render_args.callback)(false, window, cx)
17751    })
17752    .unwrap();
17753    let snapshot = editor
17754        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
17755        .unwrap();
17756    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
17757}
17758
17759#[gpui::test]
17760async fn test_input_text(cx: &mut TestAppContext) {
17761    init_test(cx, |_| {});
17762    let mut cx = EditorTestContext::new(cx).await;
17763
17764    cx.set_state(
17765        &r#"ˇone
17766        two
17767
17768        three
17769        fourˇ
17770        five
17771
17772        siˇx"#
17773            .unindent(),
17774    );
17775
17776    cx.dispatch_action(HandleInput(String::new()));
17777    cx.assert_editor_state(
17778        &r#"ˇone
17779        two
17780
17781        three
17782        fourˇ
17783        five
17784
17785        siˇx"#
17786            .unindent(),
17787    );
17788
17789    cx.dispatch_action(HandleInput("AAAA".to_string()));
17790    cx.assert_editor_state(
17791        &r#"AAAAˇone
17792        two
17793
17794        three
17795        fourAAAAˇ
17796        five
17797
17798        siAAAAˇx"#
17799            .unindent(),
17800    );
17801}
17802
17803#[gpui::test]
17804async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
17805    init_test(cx, |_| {});
17806
17807    let mut cx = EditorTestContext::new(cx).await;
17808    cx.set_state(
17809        r#"let foo = 1;
17810let foo = 2;
17811let foo = 3;
17812let fooˇ = 4;
17813let foo = 5;
17814let foo = 6;
17815let foo = 7;
17816let foo = 8;
17817let foo = 9;
17818let foo = 10;
17819let foo = 11;
17820let foo = 12;
17821let foo = 13;
17822let foo = 14;
17823let foo = 15;"#,
17824    );
17825
17826    cx.update_editor(|e, window, cx| {
17827        assert_eq!(
17828            e.next_scroll_position,
17829            NextScrollCursorCenterTopBottom::Center,
17830            "Default next scroll direction is center",
17831        );
17832
17833        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17834        assert_eq!(
17835            e.next_scroll_position,
17836            NextScrollCursorCenterTopBottom::Top,
17837            "After center, next scroll direction should be top",
17838        );
17839
17840        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17841        assert_eq!(
17842            e.next_scroll_position,
17843            NextScrollCursorCenterTopBottom::Bottom,
17844            "After top, next scroll direction should be bottom",
17845        );
17846
17847        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17848        assert_eq!(
17849            e.next_scroll_position,
17850            NextScrollCursorCenterTopBottom::Center,
17851            "After bottom, scrolling should start over",
17852        );
17853
17854        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
17855        assert_eq!(
17856            e.next_scroll_position,
17857            NextScrollCursorCenterTopBottom::Top,
17858            "Scrolling continues if retriggered fast enough"
17859        );
17860    });
17861
17862    cx.executor()
17863        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
17864    cx.executor().run_until_parked();
17865    cx.update_editor(|e, _, _| {
17866        assert_eq!(
17867            e.next_scroll_position,
17868            NextScrollCursorCenterTopBottom::Center,
17869            "If scrolling is not triggered fast enough, it should reset"
17870        );
17871    });
17872}
17873
17874#[gpui::test]
17875async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
17876    init_test(cx, |_| {});
17877    let mut cx = EditorLspTestContext::new_rust(
17878        lsp::ServerCapabilities {
17879            definition_provider: Some(lsp::OneOf::Left(true)),
17880            references_provider: Some(lsp::OneOf::Left(true)),
17881            ..lsp::ServerCapabilities::default()
17882        },
17883        cx,
17884    )
17885    .await;
17886
17887    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
17888        let go_to_definition = cx
17889            .lsp
17890            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
17891                move |params, _| async move {
17892                    if empty_go_to_definition {
17893                        Ok(None)
17894                    } else {
17895                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
17896                            uri: params.text_document_position_params.text_document.uri,
17897                            range: lsp::Range::new(
17898                                lsp::Position::new(4, 3),
17899                                lsp::Position::new(4, 6),
17900                            ),
17901                        })))
17902                    }
17903                },
17904            );
17905        let references = cx
17906            .lsp
17907            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
17908                Ok(Some(vec![lsp::Location {
17909                    uri: params.text_document_position.text_document.uri,
17910                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
17911                }]))
17912            });
17913        (go_to_definition, references)
17914    };
17915
17916    cx.set_state(
17917        &r#"fn one() {
17918            let mut a = ˇtwo();
17919        }
17920
17921        fn two() {}"#
17922            .unindent(),
17923    );
17924    set_up_lsp_handlers(false, &mut cx);
17925    let navigated = cx
17926        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17927        .await
17928        .expect("Failed to navigate to definition");
17929    assert_eq!(
17930        navigated,
17931        Navigated::Yes,
17932        "Should have navigated to definition from the GetDefinition response"
17933    );
17934    cx.assert_editor_state(
17935        &r#"fn one() {
17936            let mut a = two();
17937        }
17938
17939        fn «twoˇ»() {}"#
17940            .unindent(),
17941    );
17942
17943    let editors = cx.update_workspace(|workspace, _, cx| {
17944        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17945    });
17946    cx.update_editor(|_, _, test_editor_cx| {
17947        assert_eq!(
17948            editors.len(),
17949            1,
17950            "Initially, only one, test, editor should be open in the workspace"
17951        );
17952        assert_eq!(
17953            test_editor_cx.entity(),
17954            editors.last().expect("Asserted len is 1").clone()
17955        );
17956    });
17957
17958    set_up_lsp_handlers(true, &mut cx);
17959    let navigated = cx
17960        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
17961        .await
17962        .expect("Failed to navigate to lookup references");
17963    assert_eq!(
17964        navigated,
17965        Navigated::Yes,
17966        "Should have navigated to references as a fallback after empty GoToDefinition response"
17967    );
17968    // We should not change the selections in the existing file,
17969    // if opening another milti buffer with the references
17970    cx.assert_editor_state(
17971        &r#"fn one() {
17972            let mut a = two();
17973        }
17974
17975        fn «twoˇ»() {}"#
17976            .unindent(),
17977    );
17978    let editors = cx.update_workspace(|workspace, _, cx| {
17979        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
17980    });
17981    cx.update_editor(|_, _, test_editor_cx| {
17982        assert_eq!(
17983            editors.len(),
17984            2,
17985            "After falling back to references search, we open a new editor with the results"
17986        );
17987        let references_fallback_text = editors
17988            .into_iter()
17989            .find(|new_editor| *new_editor != test_editor_cx.entity())
17990            .expect("Should have one non-test editor now")
17991            .read(test_editor_cx)
17992            .text(test_editor_cx);
17993        assert_eq!(
17994            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
17995            "Should use the range from the references response and not the GoToDefinition one"
17996        );
17997    });
17998}
17999
18000#[gpui::test]
18001async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18002    init_test(cx, |_| {});
18003    cx.update(|cx| {
18004        let mut editor_settings = EditorSettings::get_global(cx).clone();
18005        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18006        EditorSettings::override_global(editor_settings, cx);
18007    });
18008    let mut cx = EditorLspTestContext::new_rust(
18009        lsp::ServerCapabilities {
18010            definition_provider: Some(lsp::OneOf::Left(true)),
18011            references_provider: Some(lsp::OneOf::Left(true)),
18012            ..lsp::ServerCapabilities::default()
18013        },
18014        cx,
18015    )
18016    .await;
18017    let original_state = r#"fn one() {
18018        let mut a = ˇtwo();
18019    }
18020
18021    fn two() {}"#
18022        .unindent();
18023    cx.set_state(&original_state);
18024
18025    let mut go_to_definition = cx
18026        .lsp
18027        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18028            move |_, _| async move { Ok(None) },
18029        );
18030    let _references = cx
18031        .lsp
18032        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18033            panic!("Should not call for references with no go to definition fallback")
18034        });
18035
18036    let navigated = cx
18037        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18038        .await
18039        .expect("Failed to navigate to lookup references");
18040    go_to_definition
18041        .next()
18042        .await
18043        .expect("Should have called the go_to_definition handler");
18044
18045    assert_eq!(
18046        navigated,
18047        Navigated::No,
18048        "Should have navigated to references as a fallback after empty GoToDefinition response"
18049    );
18050    cx.assert_editor_state(&original_state);
18051    let editors = cx.update_workspace(|workspace, _, cx| {
18052        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18053    });
18054    cx.update_editor(|_, _, _| {
18055        assert_eq!(
18056            editors.len(),
18057            1,
18058            "After unsuccessful fallback, no other editor should have been opened"
18059        );
18060    });
18061}
18062
18063#[gpui::test]
18064async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18065    init_test(cx, |_| {});
18066
18067    let language = Arc::new(Language::new(
18068        LanguageConfig::default(),
18069        Some(tree_sitter_rust::LANGUAGE.into()),
18070    ));
18071
18072    let text = r#"
18073        #[cfg(test)]
18074        mod tests() {
18075            #[test]
18076            fn runnable_1() {
18077                let a = 1;
18078            }
18079
18080            #[test]
18081            fn runnable_2() {
18082                let a = 1;
18083                let b = 2;
18084            }
18085        }
18086    "#
18087    .unindent();
18088
18089    let fs = FakeFs::new(cx.executor());
18090    fs.insert_file("/file.rs", Default::default()).await;
18091
18092    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18093    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18094    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18095    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18096    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18097
18098    let editor = cx.new_window_entity(|window, cx| {
18099        Editor::new(
18100            EditorMode::full(),
18101            multi_buffer,
18102            Some(project.clone()),
18103            window,
18104            cx,
18105        )
18106    });
18107
18108    editor.update_in(cx, |editor, window, cx| {
18109        let snapshot = editor.buffer().read(cx).snapshot(cx);
18110        editor.tasks.insert(
18111            (buffer.read(cx).remote_id(), 3),
18112            RunnableTasks {
18113                templates: vec![],
18114                offset: snapshot.anchor_before(43),
18115                column: 0,
18116                extra_variables: HashMap::default(),
18117                context_range: BufferOffset(43)..BufferOffset(85),
18118            },
18119        );
18120        editor.tasks.insert(
18121            (buffer.read(cx).remote_id(), 8),
18122            RunnableTasks {
18123                templates: vec![],
18124                offset: snapshot.anchor_before(86),
18125                column: 0,
18126                extra_variables: HashMap::default(),
18127                context_range: BufferOffset(86)..BufferOffset(191),
18128            },
18129        );
18130
18131        // Test finding task when cursor is inside function body
18132        editor.change_selections(None, window, cx, |s| {
18133            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
18134        });
18135        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18136        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
18137
18138        // Test finding task when cursor is on function name
18139        editor.change_selections(None, window, cx, |s| {
18140            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
18141        });
18142        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
18143        assert_eq!(row, 8, "Should find task when cursor is on function name");
18144    });
18145}
18146
18147#[gpui::test]
18148async fn test_folding_buffers(cx: &mut TestAppContext) {
18149    init_test(cx, |_| {});
18150
18151    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18152    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
18153    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
18154
18155    let fs = FakeFs::new(cx.executor());
18156    fs.insert_tree(
18157        path!("/a"),
18158        json!({
18159            "first.rs": sample_text_1,
18160            "second.rs": sample_text_2,
18161            "third.rs": sample_text_3,
18162        }),
18163    )
18164    .await;
18165    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18166    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18167    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18168    let worktree = project.update(cx, |project, cx| {
18169        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18170        assert_eq!(worktrees.len(), 1);
18171        worktrees.pop().unwrap()
18172    });
18173    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18174
18175    let buffer_1 = project
18176        .update(cx, |project, cx| {
18177            project.open_buffer((worktree_id, "first.rs"), cx)
18178        })
18179        .await
18180        .unwrap();
18181    let buffer_2 = project
18182        .update(cx, |project, cx| {
18183            project.open_buffer((worktree_id, "second.rs"), cx)
18184        })
18185        .await
18186        .unwrap();
18187    let buffer_3 = project
18188        .update(cx, |project, cx| {
18189            project.open_buffer((worktree_id, "third.rs"), cx)
18190        })
18191        .await
18192        .unwrap();
18193
18194    let multi_buffer = cx.new(|cx| {
18195        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18196        multi_buffer.push_excerpts(
18197            buffer_1.clone(),
18198            [
18199                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18200                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18201                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18202            ],
18203            cx,
18204        );
18205        multi_buffer.push_excerpts(
18206            buffer_2.clone(),
18207            [
18208                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18209                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18210                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18211            ],
18212            cx,
18213        );
18214        multi_buffer.push_excerpts(
18215            buffer_3.clone(),
18216            [
18217                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18218                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18219                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18220            ],
18221            cx,
18222        );
18223        multi_buffer
18224    });
18225    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18226        Editor::new(
18227            EditorMode::full(),
18228            multi_buffer.clone(),
18229            Some(project.clone()),
18230            window,
18231            cx,
18232        )
18233    });
18234
18235    assert_eq!(
18236        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18237        "\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",
18238    );
18239
18240    multi_buffer_editor.update(cx, |editor, cx| {
18241        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18242    });
18243    assert_eq!(
18244        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18245        "\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",
18246        "After folding the first buffer, its text should not be displayed"
18247    );
18248
18249    multi_buffer_editor.update(cx, |editor, cx| {
18250        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18251    });
18252    assert_eq!(
18253        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18254        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
18255        "After folding the second buffer, its text should not be displayed"
18256    );
18257
18258    multi_buffer_editor.update(cx, |editor, cx| {
18259        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18260    });
18261    assert_eq!(
18262        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18263        "\n\n\n\n\n",
18264        "After folding the third buffer, its text should not be displayed"
18265    );
18266
18267    // Emulate selection inside the fold logic, that should work
18268    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18269        editor
18270            .snapshot(window, cx)
18271            .next_line_boundary(Point::new(0, 4));
18272    });
18273
18274    multi_buffer_editor.update(cx, |editor, cx| {
18275        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18276    });
18277    assert_eq!(
18278        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18279        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18280        "After unfolding the second buffer, its text should be displayed"
18281    );
18282
18283    // Typing inside of buffer 1 causes that buffer to be unfolded.
18284    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18285        assert_eq!(
18286            multi_buffer
18287                .read(cx)
18288                .snapshot(cx)
18289                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
18290                .collect::<String>(),
18291            "bbbb"
18292        );
18293        editor.change_selections(None, window, cx, |selections| {
18294            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
18295        });
18296        editor.handle_input("B", window, cx);
18297    });
18298
18299    assert_eq!(
18300        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18301        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
18302        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
18303    );
18304
18305    multi_buffer_editor.update(cx, |editor, cx| {
18306        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18307    });
18308    assert_eq!(
18309        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18310        "\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",
18311        "After unfolding the all buffers, all original text should be displayed"
18312    );
18313}
18314
18315#[gpui::test]
18316async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
18317    init_test(cx, |_| {});
18318
18319    let sample_text_1 = "1111\n2222\n3333".to_string();
18320    let sample_text_2 = "4444\n5555\n6666".to_string();
18321    let sample_text_3 = "7777\n8888\n9999".to_string();
18322
18323    let fs = FakeFs::new(cx.executor());
18324    fs.insert_tree(
18325        path!("/a"),
18326        json!({
18327            "first.rs": sample_text_1,
18328            "second.rs": sample_text_2,
18329            "third.rs": sample_text_3,
18330        }),
18331    )
18332    .await;
18333    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18334    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18335    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18336    let worktree = project.update(cx, |project, cx| {
18337        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18338        assert_eq!(worktrees.len(), 1);
18339        worktrees.pop().unwrap()
18340    });
18341    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18342
18343    let buffer_1 = project
18344        .update(cx, |project, cx| {
18345            project.open_buffer((worktree_id, "first.rs"), cx)
18346        })
18347        .await
18348        .unwrap();
18349    let buffer_2 = project
18350        .update(cx, |project, cx| {
18351            project.open_buffer((worktree_id, "second.rs"), cx)
18352        })
18353        .await
18354        .unwrap();
18355    let buffer_3 = project
18356        .update(cx, |project, cx| {
18357            project.open_buffer((worktree_id, "third.rs"), cx)
18358        })
18359        .await
18360        .unwrap();
18361
18362    let multi_buffer = cx.new(|cx| {
18363        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18364        multi_buffer.push_excerpts(
18365            buffer_1.clone(),
18366            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18367            cx,
18368        );
18369        multi_buffer.push_excerpts(
18370            buffer_2.clone(),
18371            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18372            cx,
18373        );
18374        multi_buffer.push_excerpts(
18375            buffer_3.clone(),
18376            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
18377            cx,
18378        );
18379        multi_buffer
18380    });
18381
18382    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18383        Editor::new(
18384            EditorMode::full(),
18385            multi_buffer,
18386            Some(project.clone()),
18387            window,
18388            cx,
18389        )
18390    });
18391
18392    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
18393    assert_eq!(
18394        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18395        full_text,
18396    );
18397
18398    multi_buffer_editor.update(cx, |editor, cx| {
18399        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
18400    });
18401    assert_eq!(
18402        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18403        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
18404        "After folding the first buffer, its text should not be displayed"
18405    );
18406
18407    multi_buffer_editor.update(cx, |editor, cx| {
18408        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
18409    });
18410
18411    assert_eq!(
18412        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18413        "\n\n\n\n\n\n7777\n8888\n9999",
18414        "After folding the second buffer, its text should not be displayed"
18415    );
18416
18417    multi_buffer_editor.update(cx, |editor, cx| {
18418        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
18419    });
18420    assert_eq!(
18421        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18422        "\n\n\n\n\n",
18423        "After folding the third buffer, its text should not be displayed"
18424    );
18425
18426    multi_buffer_editor.update(cx, |editor, cx| {
18427        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
18428    });
18429    assert_eq!(
18430        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18431        "\n\n\n\n4444\n5555\n6666\n\n",
18432        "After unfolding the second buffer, its text should be displayed"
18433    );
18434
18435    multi_buffer_editor.update(cx, |editor, cx| {
18436        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
18437    });
18438    assert_eq!(
18439        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18440        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
18441        "After unfolding the first buffer, its text should be displayed"
18442    );
18443
18444    multi_buffer_editor.update(cx, |editor, cx| {
18445        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
18446    });
18447    assert_eq!(
18448        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18449        full_text,
18450        "After unfolding all buffers, all original text should be displayed"
18451    );
18452}
18453
18454#[gpui::test]
18455async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457
18458    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
18459
18460    let fs = FakeFs::new(cx.executor());
18461    fs.insert_tree(
18462        path!("/a"),
18463        json!({
18464            "main.rs": sample_text,
18465        }),
18466    )
18467    .await;
18468    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18469    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18470    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18471    let worktree = project.update(cx, |project, cx| {
18472        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
18473        assert_eq!(worktrees.len(), 1);
18474        worktrees.pop().unwrap()
18475    });
18476    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
18477
18478    let buffer_1 = project
18479        .update(cx, |project, cx| {
18480            project.open_buffer((worktree_id, "main.rs"), cx)
18481        })
18482        .await
18483        .unwrap();
18484
18485    let multi_buffer = cx.new(|cx| {
18486        let mut multi_buffer = MultiBuffer::new(ReadWrite);
18487        multi_buffer.push_excerpts(
18488            buffer_1.clone(),
18489            [ExcerptRange::new(
18490                Point::new(0, 0)
18491                    ..Point::new(
18492                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
18493                        0,
18494                    ),
18495            )],
18496            cx,
18497        );
18498        multi_buffer
18499    });
18500    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18501        Editor::new(
18502            EditorMode::full(),
18503            multi_buffer,
18504            Some(project.clone()),
18505            window,
18506            cx,
18507        )
18508    });
18509
18510    let selection_range = Point::new(1, 0)..Point::new(2, 0);
18511    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18512        enum TestHighlight {}
18513        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
18514        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
18515        editor.highlight_text::<TestHighlight>(
18516            vec![highlight_range.clone()],
18517            HighlightStyle::color(Hsla::green()),
18518            cx,
18519        );
18520        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
18521    });
18522
18523    let full_text = format!("\n\n{sample_text}");
18524    assert_eq!(
18525        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
18526        full_text,
18527    );
18528}
18529
18530#[gpui::test]
18531async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
18532    init_test(cx, |_| {});
18533    cx.update(|cx| {
18534        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
18535            "keymaps/default-linux.json",
18536            cx,
18537        )
18538        .unwrap();
18539        cx.bind_keys(default_key_bindings);
18540    });
18541
18542    let (editor, cx) = cx.add_window_view(|window, cx| {
18543        let multi_buffer = MultiBuffer::build_multi(
18544            [
18545                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
18546                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
18547                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
18548                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
18549            ],
18550            cx,
18551        );
18552        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
18553
18554        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
18555        // fold all but the second buffer, so that we test navigating between two
18556        // adjacent folded buffers, as well as folded buffers at the start and
18557        // end the multibuffer
18558        editor.fold_buffer(buffer_ids[0], cx);
18559        editor.fold_buffer(buffer_ids[2], cx);
18560        editor.fold_buffer(buffer_ids[3], cx);
18561
18562        editor
18563    });
18564    cx.simulate_resize(size(px(1000.), px(1000.)));
18565
18566    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
18567    cx.assert_excerpts_with_selections(indoc! {"
18568        [EXCERPT]
18569        ˇ[FOLDED]
18570        [EXCERPT]
18571        a1
18572        b1
18573        [EXCERPT]
18574        [FOLDED]
18575        [EXCERPT]
18576        [FOLDED]
18577        "
18578    });
18579    cx.simulate_keystroke("down");
18580    cx.assert_excerpts_with_selections(indoc! {"
18581        [EXCERPT]
18582        [FOLDED]
18583        [EXCERPT]
18584        ˇa1
18585        b1
18586        [EXCERPT]
18587        [FOLDED]
18588        [EXCERPT]
18589        [FOLDED]
18590        "
18591    });
18592    cx.simulate_keystroke("down");
18593    cx.assert_excerpts_with_selections(indoc! {"
18594        [EXCERPT]
18595        [FOLDED]
18596        [EXCERPT]
18597        a1
18598        ˇb1
18599        [EXCERPT]
18600        [FOLDED]
18601        [EXCERPT]
18602        [FOLDED]
18603        "
18604    });
18605    cx.simulate_keystroke("down");
18606    cx.assert_excerpts_with_selections(indoc! {"
18607        [EXCERPT]
18608        [FOLDED]
18609        [EXCERPT]
18610        a1
18611        b1
18612        ˇ[EXCERPT]
18613        [FOLDED]
18614        [EXCERPT]
18615        [FOLDED]
18616        "
18617    });
18618    cx.simulate_keystroke("down");
18619    cx.assert_excerpts_with_selections(indoc! {"
18620        [EXCERPT]
18621        [FOLDED]
18622        [EXCERPT]
18623        a1
18624        b1
18625        [EXCERPT]
18626        ˇ[FOLDED]
18627        [EXCERPT]
18628        [FOLDED]
18629        "
18630    });
18631    for _ in 0..5 {
18632        cx.simulate_keystroke("down");
18633        cx.assert_excerpts_with_selections(indoc! {"
18634            [EXCERPT]
18635            [FOLDED]
18636            [EXCERPT]
18637            a1
18638            b1
18639            [EXCERPT]
18640            [FOLDED]
18641            [EXCERPT]
18642            ˇ[FOLDED]
18643            "
18644        });
18645    }
18646
18647    cx.simulate_keystroke("up");
18648    cx.assert_excerpts_with_selections(indoc! {"
18649        [EXCERPT]
18650        [FOLDED]
18651        [EXCERPT]
18652        a1
18653        b1
18654        [EXCERPT]
18655        ˇ[FOLDED]
18656        [EXCERPT]
18657        [FOLDED]
18658        "
18659    });
18660    cx.simulate_keystroke("up");
18661    cx.assert_excerpts_with_selections(indoc! {"
18662        [EXCERPT]
18663        [FOLDED]
18664        [EXCERPT]
18665        a1
18666        b1
18667        ˇ[EXCERPT]
18668        [FOLDED]
18669        [EXCERPT]
18670        [FOLDED]
18671        "
18672    });
18673    cx.simulate_keystroke("up");
18674    cx.assert_excerpts_with_selections(indoc! {"
18675        [EXCERPT]
18676        [FOLDED]
18677        [EXCERPT]
18678        a1
18679        ˇb1
18680        [EXCERPT]
18681        [FOLDED]
18682        [EXCERPT]
18683        [FOLDED]
18684        "
18685    });
18686    cx.simulate_keystroke("up");
18687    cx.assert_excerpts_with_selections(indoc! {"
18688        [EXCERPT]
18689        [FOLDED]
18690        [EXCERPT]
18691        ˇa1
18692        b1
18693        [EXCERPT]
18694        [FOLDED]
18695        [EXCERPT]
18696        [FOLDED]
18697        "
18698    });
18699    for _ in 0..5 {
18700        cx.simulate_keystroke("up");
18701        cx.assert_excerpts_with_selections(indoc! {"
18702            [EXCERPT]
18703            ˇ[FOLDED]
18704            [EXCERPT]
18705            a1
18706            b1
18707            [EXCERPT]
18708            [FOLDED]
18709            [EXCERPT]
18710            [FOLDED]
18711            "
18712        });
18713    }
18714}
18715
18716#[gpui::test]
18717async fn test_inline_completion_text(cx: &mut TestAppContext) {
18718    init_test(cx, |_| {});
18719
18720    // Simple insertion
18721    assert_highlighted_edits(
18722        "Hello, world!",
18723        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
18724        true,
18725        cx,
18726        |highlighted_edits, cx| {
18727            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
18728            assert_eq!(highlighted_edits.highlights.len(), 1);
18729            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
18730            assert_eq!(
18731                highlighted_edits.highlights[0].1.background_color,
18732                Some(cx.theme().status().created_background)
18733            );
18734        },
18735    )
18736    .await;
18737
18738    // Replacement
18739    assert_highlighted_edits(
18740        "This is a test.",
18741        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
18742        false,
18743        cx,
18744        |highlighted_edits, cx| {
18745            assert_eq!(highlighted_edits.text, "That is a test.");
18746            assert_eq!(highlighted_edits.highlights.len(), 1);
18747            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
18748            assert_eq!(
18749                highlighted_edits.highlights[0].1.background_color,
18750                Some(cx.theme().status().created_background)
18751            );
18752        },
18753    )
18754    .await;
18755
18756    // Multiple edits
18757    assert_highlighted_edits(
18758        "Hello, world!",
18759        vec![
18760            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
18761            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
18762        ],
18763        false,
18764        cx,
18765        |highlighted_edits, cx| {
18766            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
18767            assert_eq!(highlighted_edits.highlights.len(), 2);
18768            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
18769            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
18770            assert_eq!(
18771                highlighted_edits.highlights[0].1.background_color,
18772                Some(cx.theme().status().created_background)
18773            );
18774            assert_eq!(
18775                highlighted_edits.highlights[1].1.background_color,
18776                Some(cx.theme().status().created_background)
18777            );
18778        },
18779    )
18780    .await;
18781
18782    // Multiple lines with edits
18783    assert_highlighted_edits(
18784        "First line\nSecond line\nThird line\nFourth line",
18785        vec![
18786            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
18787            (
18788                Point::new(2, 0)..Point::new(2, 10),
18789                "New third line".to_string(),
18790            ),
18791            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
18792        ],
18793        false,
18794        cx,
18795        |highlighted_edits, cx| {
18796            assert_eq!(
18797                highlighted_edits.text,
18798                "Second modified\nNew third line\nFourth updated line"
18799            );
18800            assert_eq!(highlighted_edits.highlights.len(), 3);
18801            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
18802            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
18803            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
18804            for highlight in &highlighted_edits.highlights {
18805                assert_eq!(
18806                    highlight.1.background_color,
18807                    Some(cx.theme().status().created_background)
18808                );
18809            }
18810        },
18811    )
18812    .await;
18813}
18814
18815#[gpui::test]
18816async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
18817    init_test(cx, |_| {});
18818
18819    // Deletion
18820    assert_highlighted_edits(
18821        "Hello, world!",
18822        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
18823        true,
18824        cx,
18825        |highlighted_edits, cx| {
18826            assert_eq!(highlighted_edits.text, "Hello, world!");
18827            assert_eq!(highlighted_edits.highlights.len(), 1);
18828            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
18829            assert_eq!(
18830                highlighted_edits.highlights[0].1.background_color,
18831                Some(cx.theme().status().deleted_background)
18832            );
18833        },
18834    )
18835    .await;
18836
18837    // Insertion
18838    assert_highlighted_edits(
18839        "Hello, world!",
18840        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
18841        true,
18842        cx,
18843        |highlighted_edits, cx| {
18844            assert_eq!(highlighted_edits.highlights.len(), 1);
18845            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
18846            assert_eq!(
18847                highlighted_edits.highlights[0].1.background_color,
18848                Some(cx.theme().status().created_background)
18849            );
18850        },
18851    )
18852    .await;
18853}
18854
18855async fn assert_highlighted_edits(
18856    text: &str,
18857    edits: Vec<(Range<Point>, String)>,
18858    include_deletions: bool,
18859    cx: &mut TestAppContext,
18860    assertion_fn: impl Fn(HighlightedText, &App),
18861) {
18862    let window = cx.add_window(|window, cx| {
18863        let buffer = MultiBuffer::build_simple(text, cx);
18864        Editor::new(EditorMode::full(), buffer, None, window, cx)
18865    });
18866    let cx = &mut VisualTestContext::from_window(*window, cx);
18867
18868    let (buffer, snapshot) = window
18869        .update(cx, |editor, _window, cx| {
18870            (
18871                editor.buffer().clone(),
18872                editor.buffer().read(cx).snapshot(cx),
18873            )
18874        })
18875        .unwrap();
18876
18877    let edits = edits
18878        .into_iter()
18879        .map(|(range, edit)| {
18880            (
18881                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
18882                edit,
18883            )
18884        })
18885        .collect::<Vec<_>>();
18886
18887    let text_anchor_edits = edits
18888        .clone()
18889        .into_iter()
18890        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
18891        .collect::<Vec<_>>();
18892
18893    let edit_preview = window
18894        .update(cx, |_, _window, cx| {
18895            buffer
18896                .read(cx)
18897                .as_singleton()
18898                .unwrap()
18899                .read(cx)
18900                .preview_edits(text_anchor_edits.into(), cx)
18901        })
18902        .unwrap()
18903        .await;
18904
18905    cx.update(|_window, cx| {
18906        let highlighted_edits = inline_completion_edit_text(
18907            &snapshot.as_singleton().unwrap().2,
18908            &edits,
18909            &edit_preview,
18910            include_deletions,
18911            cx,
18912        );
18913        assertion_fn(highlighted_edits, cx)
18914    });
18915}
18916
18917#[track_caller]
18918fn assert_breakpoint(
18919    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
18920    path: &Arc<Path>,
18921    expected: Vec<(u32, Breakpoint)>,
18922) {
18923    if expected.len() == 0usize {
18924        assert!(!breakpoints.contains_key(path), "{}", path.display());
18925    } else {
18926        let mut breakpoint = breakpoints
18927            .get(path)
18928            .unwrap()
18929            .into_iter()
18930            .map(|breakpoint| {
18931                (
18932                    breakpoint.row,
18933                    Breakpoint {
18934                        message: breakpoint.message.clone(),
18935                        state: breakpoint.state,
18936                        condition: breakpoint.condition.clone(),
18937                        hit_condition: breakpoint.hit_condition.clone(),
18938                    },
18939                )
18940            })
18941            .collect::<Vec<_>>();
18942
18943        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
18944
18945        assert_eq!(expected, breakpoint);
18946    }
18947}
18948
18949fn add_log_breakpoint_at_cursor(
18950    editor: &mut Editor,
18951    log_message: &str,
18952    window: &mut Window,
18953    cx: &mut Context<Editor>,
18954) {
18955    let (anchor, bp) = editor
18956        .breakpoints_at_cursors(window, cx)
18957        .first()
18958        .and_then(|(anchor, bp)| {
18959            if let Some(bp) = bp {
18960                Some((*anchor, bp.clone()))
18961            } else {
18962                None
18963            }
18964        })
18965        .unwrap_or_else(|| {
18966            let cursor_position: Point = editor.selections.newest(cx).head();
18967
18968            let breakpoint_position = editor
18969                .snapshot(window, cx)
18970                .display_snapshot
18971                .buffer_snapshot
18972                .anchor_before(Point::new(cursor_position.row, 0));
18973
18974            (breakpoint_position, Breakpoint::new_log(&log_message))
18975        });
18976
18977    editor.edit_breakpoint_at_anchor(
18978        anchor,
18979        bp,
18980        BreakpointEditAction::EditLogMessage(log_message.into()),
18981        cx,
18982    );
18983}
18984
18985#[gpui::test]
18986async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
18987    init_test(cx, |_| {});
18988
18989    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
18990    let fs = FakeFs::new(cx.executor());
18991    fs.insert_tree(
18992        path!("/a"),
18993        json!({
18994            "main.rs": sample_text,
18995        }),
18996    )
18997    .await;
18998    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18999    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19000    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19001
19002    let fs = FakeFs::new(cx.executor());
19003    fs.insert_tree(
19004        path!("/a"),
19005        json!({
19006            "main.rs": sample_text,
19007        }),
19008    )
19009    .await;
19010    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19011    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19012    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19013    let worktree_id = workspace
19014        .update(cx, |workspace, _window, cx| {
19015            workspace.project().update(cx, |project, cx| {
19016                project.worktrees(cx).next().unwrap().read(cx).id()
19017            })
19018        })
19019        .unwrap();
19020
19021    let buffer = project
19022        .update(cx, |project, cx| {
19023            project.open_buffer((worktree_id, "main.rs"), cx)
19024        })
19025        .await
19026        .unwrap();
19027
19028    let (editor, cx) = cx.add_window_view(|window, cx| {
19029        Editor::new(
19030            EditorMode::full(),
19031            MultiBuffer::build_from_buffer(buffer, cx),
19032            Some(project.clone()),
19033            window,
19034            cx,
19035        )
19036    });
19037
19038    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19039    let abs_path = project.read_with(cx, |project, cx| {
19040        project
19041            .absolute_path(&project_path, cx)
19042            .map(|path_buf| Arc::from(path_buf.to_owned()))
19043            .unwrap()
19044    });
19045
19046    // assert we can add breakpoint on the first line
19047    editor.update_in(cx, |editor, window, cx| {
19048        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19049        editor.move_to_end(&MoveToEnd, window, cx);
19050        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19051    });
19052
19053    let breakpoints = editor.update(cx, |editor, cx| {
19054        editor
19055            .breakpoint_store()
19056            .as_ref()
19057            .unwrap()
19058            .read(cx)
19059            .all_source_breakpoints(cx)
19060            .clone()
19061    });
19062
19063    assert_eq!(1, breakpoints.len());
19064    assert_breakpoint(
19065        &breakpoints,
19066        &abs_path,
19067        vec![
19068            (0, Breakpoint::new_standard()),
19069            (3, Breakpoint::new_standard()),
19070        ],
19071    );
19072
19073    editor.update_in(cx, |editor, window, cx| {
19074        editor.move_to_beginning(&MoveToBeginning, window, cx);
19075        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19076    });
19077
19078    let breakpoints = editor.update(cx, |editor, cx| {
19079        editor
19080            .breakpoint_store()
19081            .as_ref()
19082            .unwrap()
19083            .read(cx)
19084            .all_source_breakpoints(cx)
19085            .clone()
19086    });
19087
19088    assert_eq!(1, breakpoints.len());
19089    assert_breakpoint(
19090        &breakpoints,
19091        &abs_path,
19092        vec![(3, Breakpoint::new_standard())],
19093    );
19094
19095    editor.update_in(cx, |editor, window, cx| {
19096        editor.move_to_end(&MoveToEnd, window, cx);
19097        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19098    });
19099
19100    let breakpoints = editor.update(cx, |editor, cx| {
19101        editor
19102            .breakpoint_store()
19103            .as_ref()
19104            .unwrap()
19105            .read(cx)
19106            .all_source_breakpoints(cx)
19107            .clone()
19108    });
19109
19110    assert_eq!(0, breakpoints.len());
19111    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19112}
19113
19114#[gpui::test]
19115async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19116    init_test(cx, |_| {});
19117
19118    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19119
19120    let fs = FakeFs::new(cx.executor());
19121    fs.insert_tree(
19122        path!("/a"),
19123        json!({
19124            "main.rs": sample_text,
19125        }),
19126    )
19127    .await;
19128    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19129    let (workspace, cx) =
19130        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19131
19132    let worktree_id = workspace.update(cx, |workspace, cx| {
19133        workspace.project().update(cx, |project, cx| {
19134            project.worktrees(cx).next().unwrap().read(cx).id()
19135        })
19136    });
19137
19138    let buffer = project
19139        .update(cx, |project, cx| {
19140            project.open_buffer((worktree_id, "main.rs"), cx)
19141        })
19142        .await
19143        .unwrap();
19144
19145    let (editor, cx) = cx.add_window_view(|window, cx| {
19146        Editor::new(
19147            EditorMode::full(),
19148            MultiBuffer::build_from_buffer(buffer, cx),
19149            Some(project.clone()),
19150            window,
19151            cx,
19152        )
19153    });
19154
19155    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19156    let abs_path = project.read_with(cx, |project, cx| {
19157        project
19158            .absolute_path(&project_path, cx)
19159            .map(|path_buf| Arc::from(path_buf.to_owned()))
19160            .unwrap()
19161    });
19162
19163    editor.update_in(cx, |editor, window, cx| {
19164        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19165    });
19166
19167    let breakpoints = editor.update(cx, |editor, cx| {
19168        editor
19169            .breakpoint_store()
19170            .as_ref()
19171            .unwrap()
19172            .read(cx)
19173            .all_source_breakpoints(cx)
19174            .clone()
19175    });
19176
19177    assert_breakpoint(
19178        &breakpoints,
19179        &abs_path,
19180        vec![(0, Breakpoint::new_log("hello world"))],
19181    );
19182
19183    // Removing a log message from a log breakpoint should remove it
19184    editor.update_in(cx, |editor, window, cx| {
19185        add_log_breakpoint_at_cursor(editor, "", window, cx);
19186    });
19187
19188    let breakpoints = editor.update(cx, |editor, cx| {
19189        editor
19190            .breakpoint_store()
19191            .as_ref()
19192            .unwrap()
19193            .read(cx)
19194            .all_source_breakpoints(cx)
19195            .clone()
19196    });
19197
19198    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19199
19200    editor.update_in(cx, |editor, window, cx| {
19201        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19202        editor.move_to_end(&MoveToEnd, window, cx);
19203        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19204        // Not adding a log message to a standard breakpoint shouldn't remove it
19205        add_log_breakpoint_at_cursor(editor, "", window, cx);
19206    });
19207
19208    let breakpoints = editor.update(cx, |editor, cx| {
19209        editor
19210            .breakpoint_store()
19211            .as_ref()
19212            .unwrap()
19213            .read(cx)
19214            .all_source_breakpoints(cx)
19215            .clone()
19216    });
19217
19218    assert_breakpoint(
19219        &breakpoints,
19220        &abs_path,
19221        vec![
19222            (0, Breakpoint::new_standard()),
19223            (3, Breakpoint::new_standard()),
19224        ],
19225    );
19226
19227    editor.update_in(cx, |editor, window, cx| {
19228        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
19229    });
19230
19231    let breakpoints = editor.update(cx, |editor, cx| {
19232        editor
19233            .breakpoint_store()
19234            .as_ref()
19235            .unwrap()
19236            .read(cx)
19237            .all_source_breakpoints(cx)
19238            .clone()
19239    });
19240
19241    assert_breakpoint(
19242        &breakpoints,
19243        &abs_path,
19244        vec![
19245            (0, Breakpoint::new_standard()),
19246            (3, Breakpoint::new_log("hello world")),
19247        ],
19248    );
19249
19250    editor.update_in(cx, |editor, window, cx| {
19251        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
19252    });
19253
19254    let breakpoints = editor.update(cx, |editor, cx| {
19255        editor
19256            .breakpoint_store()
19257            .as_ref()
19258            .unwrap()
19259            .read(cx)
19260            .all_source_breakpoints(cx)
19261            .clone()
19262    });
19263
19264    assert_breakpoint(
19265        &breakpoints,
19266        &abs_path,
19267        vec![
19268            (0, Breakpoint::new_standard()),
19269            (3, Breakpoint::new_log("hello Earth!!")),
19270        ],
19271    );
19272}
19273
19274/// This also tests that Editor::breakpoint_at_cursor_head is working properly
19275/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
19276/// or when breakpoints were placed out of order. This tests for a regression too
19277#[gpui::test]
19278async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
19279    init_test(cx, |_| {});
19280
19281    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19282    let fs = FakeFs::new(cx.executor());
19283    fs.insert_tree(
19284        path!("/a"),
19285        json!({
19286            "main.rs": sample_text,
19287        }),
19288    )
19289    .await;
19290    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19291    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19292    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19293
19294    let fs = FakeFs::new(cx.executor());
19295    fs.insert_tree(
19296        path!("/a"),
19297        json!({
19298            "main.rs": sample_text,
19299        }),
19300    )
19301    .await;
19302    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19303    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19304    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19305    let worktree_id = workspace
19306        .update(cx, |workspace, _window, cx| {
19307            workspace.project().update(cx, |project, cx| {
19308                project.worktrees(cx).next().unwrap().read(cx).id()
19309            })
19310        })
19311        .unwrap();
19312
19313    let buffer = project
19314        .update(cx, |project, cx| {
19315            project.open_buffer((worktree_id, "main.rs"), cx)
19316        })
19317        .await
19318        .unwrap();
19319
19320    let (editor, cx) = cx.add_window_view(|window, cx| {
19321        Editor::new(
19322            EditorMode::full(),
19323            MultiBuffer::build_from_buffer(buffer, cx),
19324            Some(project.clone()),
19325            window,
19326            cx,
19327        )
19328    });
19329
19330    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19331    let abs_path = project.read_with(cx, |project, cx| {
19332        project
19333            .absolute_path(&project_path, cx)
19334            .map(|path_buf| Arc::from(path_buf.to_owned()))
19335            .unwrap()
19336    });
19337
19338    // assert we can add breakpoint on the first line
19339    editor.update_in(cx, |editor, window, cx| {
19340        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19341        editor.move_to_end(&MoveToEnd, window, cx);
19342        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19343        editor.move_up(&MoveUp, window, cx);
19344        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19345    });
19346
19347    let breakpoints = editor.update(cx, |editor, cx| {
19348        editor
19349            .breakpoint_store()
19350            .as_ref()
19351            .unwrap()
19352            .read(cx)
19353            .all_source_breakpoints(cx)
19354            .clone()
19355    });
19356
19357    assert_eq!(1, breakpoints.len());
19358    assert_breakpoint(
19359        &breakpoints,
19360        &abs_path,
19361        vec![
19362            (0, Breakpoint::new_standard()),
19363            (2, Breakpoint::new_standard()),
19364            (3, Breakpoint::new_standard()),
19365        ],
19366    );
19367
19368    editor.update_in(cx, |editor, window, cx| {
19369        editor.move_to_beginning(&MoveToBeginning, window, cx);
19370        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19371        editor.move_to_end(&MoveToEnd, window, cx);
19372        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19373        // Disabling a breakpoint that doesn't exist should do nothing
19374        editor.move_up(&MoveUp, window, cx);
19375        editor.move_up(&MoveUp, window, cx);
19376        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19377    });
19378
19379    let breakpoints = editor.update(cx, |editor, cx| {
19380        editor
19381            .breakpoint_store()
19382            .as_ref()
19383            .unwrap()
19384            .read(cx)
19385            .all_source_breakpoints(cx)
19386            .clone()
19387    });
19388
19389    let disable_breakpoint = {
19390        let mut bp = Breakpoint::new_standard();
19391        bp.state = BreakpointState::Disabled;
19392        bp
19393    };
19394
19395    assert_eq!(1, breakpoints.len());
19396    assert_breakpoint(
19397        &breakpoints,
19398        &abs_path,
19399        vec![
19400            (0, disable_breakpoint.clone()),
19401            (2, Breakpoint::new_standard()),
19402            (3, disable_breakpoint.clone()),
19403        ],
19404    );
19405
19406    editor.update_in(cx, |editor, window, cx| {
19407        editor.move_to_beginning(&MoveToBeginning, window, cx);
19408        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19409        editor.move_to_end(&MoveToEnd, window, cx);
19410        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
19411        editor.move_up(&MoveUp, window, cx);
19412        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
19413    });
19414
19415    let breakpoints = editor.update(cx, |editor, cx| {
19416        editor
19417            .breakpoint_store()
19418            .as_ref()
19419            .unwrap()
19420            .read(cx)
19421            .all_source_breakpoints(cx)
19422            .clone()
19423    });
19424
19425    assert_eq!(1, breakpoints.len());
19426    assert_breakpoint(
19427        &breakpoints,
19428        &abs_path,
19429        vec![
19430            (0, Breakpoint::new_standard()),
19431            (2, disable_breakpoint),
19432            (3, Breakpoint::new_standard()),
19433        ],
19434    );
19435}
19436
19437#[gpui::test]
19438async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
19439    init_test(cx, |_| {});
19440    let capabilities = lsp::ServerCapabilities {
19441        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
19442            prepare_provider: Some(true),
19443            work_done_progress_options: Default::default(),
19444        })),
19445        ..Default::default()
19446    };
19447    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19448
19449    cx.set_state(indoc! {"
19450        struct Fˇoo {}
19451    "});
19452
19453    cx.update_editor(|editor, _, cx| {
19454        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19455        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19456        editor.highlight_background::<DocumentHighlightRead>(
19457            &[highlight_range],
19458            |c| c.editor_document_highlight_read_background,
19459            cx,
19460        );
19461    });
19462
19463    let mut prepare_rename_handler = cx
19464        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
19465            move |_, _, _| async move {
19466                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
19467                    start: lsp::Position {
19468                        line: 0,
19469                        character: 7,
19470                    },
19471                    end: lsp::Position {
19472                        line: 0,
19473                        character: 10,
19474                    },
19475                })))
19476            },
19477        );
19478    let prepare_rename_task = cx
19479        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19480        .expect("Prepare rename was not started");
19481    prepare_rename_handler.next().await.unwrap();
19482    prepare_rename_task.await.expect("Prepare rename failed");
19483
19484    let mut rename_handler =
19485        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19486            let edit = lsp::TextEdit {
19487                range: lsp::Range {
19488                    start: lsp::Position {
19489                        line: 0,
19490                        character: 7,
19491                    },
19492                    end: lsp::Position {
19493                        line: 0,
19494                        character: 10,
19495                    },
19496                },
19497                new_text: "FooRenamed".to_string(),
19498            };
19499            Ok(Some(lsp::WorkspaceEdit::new(
19500                // Specify the same edit twice
19501                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
19502            )))
19503        });
19504    let rename_task = cx
19505        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19506        .expect("Confirm rename was not started");
19507    rename_handler.next().await.unwrap();
19508    rename_task.await.expect("Confirm rename failed");
19509    cx.run_until_parked();
19510
19511    // Despite two edits, only one is actually applied as those are identical
19512    cx.assert_editor_state(indoc! {"
19513        struct FooRenamedˇ {}
19514    "});
19515}
19516
19517#[gpui::test]
19518async fn test_rename_without_prepare(cx: &mut TestAppContext) {
19519    init_test(cx, |_| {});
19520    // These capabilities indicate that the server does not support prepare rename.
19521    let capabilities = lsp::ServerCapabilities {
19522        rename_provider: Some(lsp::OneOf::Left(true)),
19523        ..Default::default()
19524    };
19525    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
19526
19527    cx.set_state(indoc! {"
19528        struct Fˇoo {}
19529    "});
19530
19531    cx.update_editor(|editor, _window, cx| {
19532        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
19533        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
19534        editor.highlight_background::<DocumentHighlightRead>(
19535            &[highlight_range],
19536            |c| c.editor_document_highlight_read_background,
19537            cx,
19538        );
19539    });
19540
19541    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
19542        .expect("Prepare rename was not started")
19543        .await
19544        .expect("Prepare rename failed");
19545
19546    let mut rename_handler =
19547        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
19548            let edit = lsp::TextEdit {
19549                range: lsp::Range {
19550                    start: lsp::Position {
19551                        line: 0,
19552                        character: 7,
19553                    },
19554                    end: lsp::Position {
19555                        line: 0,
19556                        character: 10,
19557                    },
19558                },
19559                new_text: "FooRenamed".to_string(),
19560            };
19561            Ok(Some(lsp::WorkspaceEdit::new(
19562                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
19563            )))
19564        });
19565    let rename_task = cx
19566        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
19567        .expect("Confirm rename was not started");
19568    rename_handler.next().await.unwrap();
19569    rename_task.await.expect("Confirm rename failed");
19570    cx.run_until_parked();
19571
19572    // Correct range is renamed, as `surrounding_word` is used to find it.
19573    cx.assert_editor_state(indoc! {"
19574        struct FooRenamedˇ {}
19575    "});
19576}
19577
19578#[gpui::test]
19579async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
19580    init_test(cx, |_| {});
19581    let mut cx = EditorTestContext::new(cx).await;
19582
19583    let language = Arc::new(
19584        Language::new(
19585            LanguageConfig::default(),
19586            Some(tree_sitter_html::LANGUAGE.into()),
19587        )
19588        .with_brackets_query(
19589            r#"
19590            ("<" @open "/>" @close)
19591            ("</" @open ">" @close)
19592            ("<" @open ">" @close)
19593            ("\"" @open "\"" @close)
19594            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
19595        "#,
19596        )
19597        .unwrap(),
19598    );
19599    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19600
19601    cx.set_state(indoc! {"
19602        <span>ˇ</span>
19603    "});
19604    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19605    cx.assert_editor_state(indoc! {"
19606        <span>
19607        ˇ
19608        </span>
19609    "});
19610
19611    cx.set_state(indoc! {"
19612        <span><span></span>ˇ</span>
19613    "});
19614    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19615    cx.assert_editor_state(indoc! {"
19616        <span><span></span>
19617        ˇ</span>
19618    "});
19619
19620    cx.set_state(indoc! {"
19621        <span>ˇ
19622        </span>
19623    "});
19624    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
19625    cx.assert_editor_state(indoc! {"
19626        <span>
19627        ˇ
19628        </span>
19629    "});
19630}
19631
19632#[gpui::test(iterations = 10)]
19633async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
19634    init_test(cx, |_| {});
19635
19636    let fs = FakeFs::new(cx.executor());
19637    fs.insert_tree(
19638        path!("/dir"),
19639        json!({
19640            "a.ts": "a",
19641        }),
19642    )
19643    .await;
19644
19645    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
19646    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19647    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19648
19649    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19650    language_registry.add(Arc::new(Language::new(
19651        LanguageConfig {
19652            name: "TypeScript".into(),
19653            matcher: LanguageMatcher {
19654                path_suffixes: vec!["ts".to_string()],
19655                ..Default::default()
19656            },
19657            ..Default::default()
19658        },
19659        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19660    )));
19661    let mut fake_language_servers = language_registry.register_fake_lsp(
19662        "TypeScript",
19663        FakeLspAdapter {
19664            capabilities: lsp::ServerCapabilities {
19665                code_lens_provider: Some(lsp::CodeLensOptions {
19666                    resolve_provider: Some(true),
19667                }),
19668                execute_command_provider: Some(lsp::ExecuteCommandOptions {
19669                    commands: vec!["_the/command".to_string()],
19670                    ..lsp::ExecuteCommandOptions::default()
19671                }),
19672                ..lsp::ServerCapabilities::default()
19673            },
19674            ..FakeLspAdapter::default()
19675        },
19676    );
19677
19678    let (buffer, _handle) = project
19679        .update(cx, |p, cx| {
19680            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
19681        })
19682        .await
19683        .unwrap();
19684    cx.executor().run_until_parked();
19685
19686    let fake_server = fake_language_servers.next().await.unwrap();
19687
19688    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
19689    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
19690    drop(buffer_snapshot);
19691    let actions = cx
19692        .update_window(*workspace, |_, window, cx| {
19693            project.code_actions(&buffer, anchor..anchor, window, cx)
19694        })
19695        .unwrap();
19696
19697    fake_server
19698        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
19699            Ok(Some(vec![
19700                lsp::CodeLens {
19701                    range: lsp::Range::default(),
19702                    command: Some(lsp::Command {
19703                        title: "Code lens command".to_owned(),
19704                        command: "_the/command".to_owned(),
19705                        arguments: None,
19706                    }),
19707                    data: None,
19708                },
19709                lsp::CodeLens {
19710                    range: lsp::Range::default(),
19711                    command: Some(lsp::Command {
19712                        title: "Command not in capabilities".to_owned(),
19713                        command: "not in capabilities".to_owned(),
19714                        arguments: None,
19715                    }),
19716                    data: None,
19717                },
19718                lsp::CodeLens {
19719                    range: lsp::Range {
19720                        start: lsp::Position {
19721                            line: 1,
19722                            character: 1,
19723                        },
19724                        end: lsp::Position {
19725                            line: 1,
19726                            character: 1,
19727                        },
19728                    },
19729                    command: Some(lsp::Command {
19730                        title: "Command not in range".to_owned(),
19731                        command: "_the/command".to_owned(),
19732                        arguments: None,
19733                    }),
19734                    data: None,
19735                },
19736            ]))
19737        })
19738        .next()
19739        .await;
19740
19741    let actions = actions.await.unwrap();
19742    assert_eq!(
19743        actions.len(),
19744        1,
19745        "Should have only one valid action for the 0..0 range"
19746    );
19747    let action = actions[0].clone();
19748    let apply = project.update(cx, |project, cx| {
19749        project.apply_code_action(buffer.clone(), action, true, cx)
19750    });
19751
19752    // Resolving the code action does not populate its edits. In absence of
19753    // edits, we must execute the given command.
19754    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
19755        |mut lens, _| async move {
19756            let lens_command = lens.command.as_mut().expect("should have a command");
19757            assert_eq!(lens_command.title, "Code lens command");
19758            lens_command.arguments = Some(vec![json!("the-argument")]);
19759            Ok(lens)
19760        },
19761    );
19762
19763    // While executing the command, the language server sends the editor
19764    // a `workspaceEdit` request.
19765    fake_server
19766        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
19767            let fake = fake_server.clone();
19768            move |params, _| {
19769                assert_eq!(params.command, "_the/command");
19770                let fake = fake.clone();
19771                async move {
19772                    fake.server
19773                        .request::<lsp::request::ApplyWorkspaceEdit>(
19774                            lsp::ApplyWorkspaceEditParams {
19775                                label: None,
19776                                edit: lsp::WorkspaceEdit {
19777                                    changes: Some(
19778                                        [(
19779                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
19780                                            vec![lsp::TextEdit {
19781                                                range: lsp::Range::new(
19782                                                    lsp::Position::new(0, 0),
19783                                                    lsp::Position::new(0, 0),
19784                                                ),
19785                                                new_text: "X".into(),
19786                                            }],
19787                                        )]
19788                                        .into_iter()
19789                                        .collect(),
19790                                    ),
19791                                    ..Default::default()
19792                                },
19793                            },
19794                        )
19795                        .await
19796                        .into_response()
19797                        .unwrap();
19798                    Ok(Some(json!(null)))
19799                }
19800            }
19801        })
19802        .next()
19803        .await;
19804
19805    // Applying the code lens command returns a project transaction containing the edits
19806    // sent by the language server in its `workspaceEdit` request.
19807    let transaction = apply.await.unwrap();
19808    assert!(transaction.0.contains_key(&buffer));
19809    buffer.update(cx, |buffer, cx| {
19810        assert_eq!(buffer.text(), "Xa");
19811        buffer.undo(cx);
19812        assert_eq!(buffer.text(), "a");
19813    });
19814}
19815
19816#[gpui::test]
19817async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
19818    init_test(cx, |_| {});
19819
19820    let fs = FakeFs::new(cx.executor());
19821    let main_text = r#"fn main() {
19822println!("1");
19823println!("2");
19824println!("3");
19825println!("4");
19826println!("5");
19827}"#;
19828    let lib_text = "mod foo {}";
19829    fs.insert_tree(
19830        path!("/a"),
19831        json!({
19832            "lib.rs": lib_text,
19833            "main.rs": main_text,
19834        }),
19835    )
19836    .await;
19837
19838    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19839    let (workspace, cx) =
19840        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
19841    let worktree_id = workspace.update(cx, |workspace, cx| {
19842        workspace.project().update(cx, |project, cx| {
19843            project.worktrees(cx).next().unwrap().read(cx).id()
19844        })
19845    });
19846
19847    let expected_ranges = vec![
19848        Point::new(0, 0)..Point::new(0, 0),
19849        Point::new(1, 0)..Point::new(1, 1),
19850        Point::new(2, 0)..Point::new(2, 2),
19851        Point::new(3, 0)..Point::new(3, 3),
19852    ];
19853
19854    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
19855    let editor_1 = workspace
19856        .update_in(cx, |workspace, window, cx| {
19857            workspace.open_path(
19858                (worktree_id, "main.rs"),
19859                Some(pane_1.downgrade()),
19860                true,
19861                window,
19862                cx,
19863            )
19864        })
19865        .unwrap()
19866        .await
19867        .downcast::<Editor>()
19868        .unwrap();
19869    pane_1.update(cx, |pane, cx| {
19870        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19871        open_editor.update(cx, |editor, cx| {
19872            assert_eq!(
19873                editor.display_text(cx),
19874                main_text,
19875                "Original main.rs text on initial open",
19876            );
19877            assert_eq!(
19878                editor
19879                    .selections
19880                    .all::<Point>(cx)
19881                    .into_iter()
19882                    .map(|s| s.range())
19883                    .collect::<Vec<_>>(),
19884                vec![Point::zero()..Point::zero()],
19885                "Default selections on initial open",
19886            );
19887        })
19888    });
19889    editor_1.update_in(cx, |editor, window, cx| {
19890        editor.change_selections(None, window, cx, |s| {
19891            s.select_ranges(expected_ranges.clone());
19892        });
19893    });
19894
19895    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
19896        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
19897    });
19898    let editor_2 = workspace
19899        .update_in(cx, |workspace, window, cx| {
19900            workspace.open_path(
19901                (worktree_id, "main.rs"),
19902                Some(pane_2.downgrade()),
19903                true,
19904                window,
19905                cx,
19906            )
19907        })
19908        .unwrap()
19909        .await
19910        .downcast::<Editor>()
19911        .unwrap();
19912    pane_2.update(cx, |pane, cx| {
19913        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19914        open_editor.update(cx, |editor, cx| {
19915            assert_eq!(
19916                editor.display_text(cx),
19917                main_text,
19918                "Original main.rs text on initial open in another panel",
19919            );
19920            assert_eq!(
19921                editor
19922                    .selections
19923                    .all::<Point>(cx)
19924                    .into_iter()
19925                    .map(|s| s.range())
19926                    .collect::<Vec<_>>(),
19927                vec![Point::zero()..Point::zero()],
19928                "Default selections on initial open in another panel",
19929            );
19930        })
19931    });
19932
19933    editor_2.update_in(cx, |editor, window, cx| {
19934        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
19935    });
19936
19937    let _other_editor_1 = workspace
19938        .update_in(cx, |workspace, window, cx| {
19939            workspace.open_path(
19940                (worktree_id, "lib.rs"),
19941                Some(pane_1.downgrade()),
19942                true,
19943                window,
19944                cx,
19945            )
19946        })
19947        .unwrap()
19948        .await
19949        .downcast::<Editor>()
19950        .unwrap();
19951    pane_1
19952        .update_in(cx, |pane, window, cx| {
19953            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19954                .unwrap()
19955        })
19956        .await
19957        .unwrap();
19958    drop(editor_1);
19959    pane_1.update(cx, |pane, cx| {
19960        pane.active_item()
19961            .unwrap()
19962            .downcast::<Editor>()
19963            .unwrap()
19964            .update(cx, |editor, cx| {
19965                assert_eq!(
19966                    editor.display_text(cx),
19967                    lib_text,
19968                    "Other file should be open and active",
19969                );
19970            });
19971        assert_eq!(pane.items().count(), 1, "No other editors should be open");
19972    });
19973
19974    let _other_editor_2 = workspace
19975        .update_in(cx, |workspace, window, cx| {
19976            workspace.open_path(
19977                (worktree_id, "lib.rs"),
19978                Some(pane_2.downgrade()),
19979                true,
19980                window,
19981                cx,
19982            )
19983        })
19984        .unwrap()
19985        .await
19986        .downcast::<Editor>()
19987        .unwrap();
19988    pane_2
19989        .update_in(cx, |pane, window, cx| {
19990            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
19991                .unwrap()
19992        })
19993        .await
19994        .unwrap();
19995    drop(editor_2);
19996    pane_2.update(cx, |pane, cx| {
19997        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
19998        open_editor.update(cx, |editor, cx| {
19999            assert_eq!(
20000                editor.display_text(cx),
20001                lib_text,
20002                "Other file should be open and active in another panel too",
20003            );
20004        });
20005        assert_eq!(
20006            pane.items().count(),
20007            1,
20008            "No other editors should be open in another pane",
20009        );
20010    });
20011
20012    let _editor_1_reopened = workspace
20013        .update_in(cx, |workspace, window, cx| {
20014            workspace.open_path(
20015                (worktree_id, "main.rs"),
20016                Some(pane_1.downgrade()),
20017                true,
20018                window,
20019                cx,
20020            )
20021        })
20022        .unwrap()
20023        .await
20024        .downcast::<Editor>()
20025        .unwrap();
20026    let _editor_2_reopened = workspace
20027        .update_in(cx, |workspace, window, cx| {
20028            workspace.open_path(
20029                (worktree_id, "main.rs"),
20030                Some(pane_2.downgrade()),
20031                true,
20032                window,
20033                cx,
20034            )
20035        })
20036        .unwrap()
20037        .await
20038        .downcast::<Editor>()
20039        .unwrap();
20040    pane_1.update(cx, |pane, cx| {
20041        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20042        open_editor.update(cx, |editor, cx| {
20043            assert_eq!(
20044                editor.display_text(cx),
20045                main_text,
20046                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20047            );
20048            assert_eq!(
20049                editor
20050                    .selections
20051                    .all::<Point>(cx)
20052                    .into_iter()
20053                    .map(|s| s.range())
20054                    .collect::<Vec<_>>(),
20055                expected_ranges,
20056                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20057            );
20058        })
20059    });
20060    pane_2.update(cx, |pane, cx| {
20061        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20062        open_editor.update(cx, |editor, cx| {
20063            assert_eq!(
20064                editor.display_text(cx),
20065                r#"fn main() {
20066⋯rintln!("1");
20067⋯intln!("2");
20068⋯ntln!("3");
20069println!("4");
20070println!("5");
20071}"#,
20072                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20073            );
20074            assert_eq!(
20075                editor
20076                    .selections
20077                    .all::<Point>(cx)
20078                    .into_iter()
20079                    .map(|s| s.range())
20080                    .collect::<Vec<_>>(),
20081                vec![Point::zero()..Point::zero()],
20082                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20083            );
20084        })
20085    });
20086}
20087
20088#[gpui::test]
20089async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20090    init_test(cx, |_| {});
20091
20092    let fs = FakeFs::new(cx.executor());
20093    let main_text = r#"fn main() {
20094println!("1");
20095println!("2");
20096println!("3");
20097println!("4");
20098println!("5");
20099}"#;
20100    let lib_text = "mod foo {}";
20101    fs.insert_tree(
20102        path!("/a"),
20103        json!({
20104            "lib.rs": lib_text,
20105            "main.rs": main_text,
20106        }),
20107    )
20108    .await;
20109
20110    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20111    let (workspace, cx) =
20112        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20113    let worktree_id = workspace.update(cx, |workspace, cx| {
20114        workspace.project().update(cx, |project, cx| {
20115            project.worktrees(cx).next().unwrap().read(cx).id()
20116        })
20117    });
20118
20119    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20120    let editor = workspace
20121        .update_in(cx, |workspace, window, cx| {
20122            workspace.open_path(
20123                (worktree_id, "main.rs"),
20124                Some(pane.downgrade()),
20125                true,
20126                window,
20127                cx,
20128            )
20129        })
20130        .unwrap()
20131        .await
20132        .downcast::<Editor>()
20133        .unwrap();
20134    pane.update(cx, |pane, cx| {
20135        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20136        open_editor.update(cx, |editor, cx| {
20137            assert_eq!(
20138                editor.display_text(cx),
20139                main_text,
20140                "Original main.rs text on initial open",
20141            );
20142        })
20143    });
20144    editor.update_in(cx, |editor, window, cx| {
20145        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
20146    });
20147
20148    cx.update_global(|store: &mut SettingsStore, cx| {
20149        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20150            s.restore_on_file_reopen = Some(false);
20151        });
20152    });
20153    editor.update_in(cx, |editor, window, cx| {
20154        editor.fold_ranges(
20155            vec![
20156                Point::new(1, 0)..Point::new(1, 1),
20157                Point::new(2, 0)..Point::new(2, 2),
20158                Point::new(3, 0)..Point::new(3, 3),
20159            ],
20160            false,
20161            window,
20162            cx,
20163        );
20164    });
20165    pane.update_in(cx, |pane, window, cx| {
20166        pane.close_all_items(&CloseAllItems::default(), window, cx)
20167            .unwrap()
20168    })
20169    .await
20170    .unwrap();
20171    pane.update(cx, |pane, _| {
20172        assert!(pane.active_item().is_none());
20173    });
20174    cx.update_global(|store: &mut SettingsStore, cx| {
20175        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
20176            s.restore_on_file_reopen = Some(true);
20177        });
20178    });
20179
20180    let _editor_reopened = workspace
20181        .update_in(cx, |workspace, window, cx| {
20182            workspace.open_path(
20183                (worktree_id, "main.rs"),
20184                Some(pane.downgrade()),
20185                true,
20186                window,
20187                cx,
20188            )
20189        })
20190        .unwrap()
20191        .await
20192        .downcast::<Editor>()
20193        .unwrap();
20194    pane.update(cx, |pane, cx| {
20195        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20196        open_editor.update(cx, |editor, cx| {
20197            assert_eq!(
20198                editor.display_text(cx),
20199                main_text,
20200                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
20201            );
20202        })
20203    });
20204}
20205
20206#[gpui::test]
20207async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
20208    struct EmptyModalView {
20209        focus_handle: gpui::FocusHandle,
20210    }
20211    impl EventEmitter<DismissEvent> for EmptyModalView {}
20212    impl Render for EmptyModalView {
20213        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
20214            div()
20215        }
20216    }
20217    impl Focusable for EmptyModalView {
20218        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
20219            self.focus_handle.clone()
20220        }
20221    }
20222    impl workspace::ModalView for EmptyModalView {}
20223    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
20224        EmptyModalView {
20225            focus_handle: cx.focus_handle(),
20226        }
20227    }
20228
20229    init_test(cx, |_| {});
20230
20231    let fs = FakeFs::new(cx.executor());
20232    let project = Project::test(fs, [], cx).await;
20233    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20234    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
20235    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20236    let editor = cx.new_window_entity(|window, cx| {
20237        Editor::new(
20238            EditorMode::full(),
20239            buffer,
20240            Some(project.clone()),
20241            window,
20242            cx,
20243        )
20244    });
20245    workspace
20246        .update(cx, |workspace, window, cx| {
20247            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
20248        })
20249        .unwrap();
20250    editor.update_in(cx, |editor, window, cx| {
20251        editor.open_context_menu(&OpenContextMenu, window, cx);
20252        assert!(editor.mouse_context_menu.is_some());
20253    });
20254    workspace
20255        .update(cx, |workspace, window, cx| {
20256            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
20257        })
20258        .unwrap();
20259    cx.read(|cx| {
20260        assert!(editor.read(cx).mouse_context_menu.is_none());
20261    });
20262}
20263
20264#[gpui::test]
20265async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
20266    init_test(cx, |_| {});
20267
20268    let fs = FakeFs::new(cx.executor());
20269    fs.insert_file(path!("/file.html"), Default::default())
20270        .await;
20271
20272    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20273
20274    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20275    let html_language = Arc::new(Language::new(
20276        LanguageConfig {
20277            name: "HTML".into(),
20278            matcher: LanguageMatcher {
20279                path_suffixes: vec!["html".to_string()],
20280                ..LanguageMatcher::default()
20281            },
20282            brackets: BracketPairConfig {
20283                pairs: vec![BracketPair {
20284                    start: "<".into(),
20285                    end: ">".into(),
20286                    close: true,
20287                    ..Default::default()
20288                }],
20289                ..Default::default()
20290            },
20291            ..Default::default()
20292        },
20293        Some(tree_sitter_html::LANGUAGE.into()),
20294    ));
20295    language_registry.add(html_language);
20296    let mut fake_servers = language_registry.register_fake_lsp(
20297        "HTML",
20298        FakeLspAdapter {
20299            capabilities: lsp::ServerCapabilities {
20300                completion_provider: Some(lsp::CompletionOptions {
20301                    resolve_provider: Some(true),
20302                    ..Default::default()
20303                }),
20304                ..Default::default()
20305            },
20306            ..Default::default()
20307        },
20308    );
20309
20310    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20311    let cx = &mut VisualTestContext::from_window(*workspace, cx);
20312
20313    let worktree_id = workspace
20314        .update(cx, |workspace, _window, cx| {
20315            workspace.project().update(cx, |project, cx| {
20316                project.worktrees(cx).next().unwrap().read(cx).id()
20317            })
20318        })
20319        .unwrap();
20320    project
20321        .update(cx, |project, cx| {
20322            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
20323        })
20324        .await
20325        .unwrap();
20326    let editor = workspace
20327        .update(cx, |workspace, window, cx| {
20328            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
20329        })
20330        .unwrap()
20331        .await
20332        .unwrap()
20333        .downcast::<Editor>()
20334        .unwrap();
20335
20336    let fake_server = fake_servers.next().await.unwrap();
20337    editor.update_in(cx, |editor, window, cx| {
20338        editor.set_text("<ad></ad>", window, cx);
20339        editor.change_selections(None, window, cx, |selections| {
20340            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
20341        });
20342        let Some((buffer, _)) = editor
20343            .buffer
20344            .read(cx)
20345            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
20346        else {
20347            panic!("Failed to get buffer for selection position");
20348        };
20349        let buffer = buffer.read(cx);
20350        let buffer_id = buffer.remote_id();
20351        let opening_range =
20352            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
20353        let closing_range =
20354            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
20355        let mut linked_ranges = HashMap::default();
20356        linked_ranges.insert(
20357            buffer_id,
20358            vec![(opening_range.clone(), vec![closing_range.clone()])],
20359        );
20360        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
20361    });
20362    let mut completion_handle =
20363        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20364            Ok(Some(lsp::CompletionResponse::Array(vec![
20365                lsp::CompletionItem {
20366                    label: "head".to_string(),
20367                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20368                        lsp::InsertReplaceEdit {
20369                            new_text: "head".to_string(),
20370                            insert: lsp::Range::new(
20371                                lsp::Position::new(0, 1),
20372                                lsp::Position::new(0, 3),
20373                            ),
20374                            replace: lsp::Range::new(
20375                                lsp::Position::new(0, 1),
20376                                lsp::Position::new(0, 3),
20377                            ),
20378                        },
20379                    )),
20380                    ..Default::default()
20381                },
20382            ])))
20383        });
20384    editor.update_in(cx, |editor, window, cx| {
20385        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
20386    });
20387    cx.run_until_parked();
20388    completion_handle.next().await.unwrap();
20389    editor.update(cx, |editor, _| {
20390        assert!(
20391            editor.context_menu_visible(),
20392            "Completion menu should be visible"
20393        );
20394    });
20395    editor.update_in(cx, |editor, window, cx| {
20396        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
20397    });
20398    cx.executor().run_until_parked();
20399    editor.update(cx, |editor, cx| {
20400        assert_eq!(editor.text(cx), "<head></head>");
20401    });
20402}
20403
20404#[gpui::test]
20405async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
20406    init_test(cx, |_| {});
20407
20408    let fs = FakeFs::new(cx.executor());
20409    fs.insert_tree(
20410        path!("/root"),
20411        json!({
20412            "a": {
20413                "main.rs": "fn main() {}",
20414            },
20415            "foo": {
20416                "bar": {
20417                    "external_file.rs": "pub mod external {}",
20418                }
20419            }
20420        }),
20421    )
20422    .await;
20423
20424    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
20425    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20426    language_registry.add(rust_lang());
20427    let _fake_servers = language_registry.register_fake_lsp(
20428        "Rust",
20429        FakeLspAdapter {
20430            ..FakeLspAdapter::default()
20431        },
20432    );
20433    let (workspace, cx) =
20434        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20435    let worktree_id = workspace.update(cx, |workspace, cx| {
20436        workspace.project().update(cx, |project, cx| {
20437            project.worktrees(cx).next().unwrap().read(cx).id()
20438        })
20439    });
20440
20441    let assert_language_servers_count =
20442        |expected: usize, context: &str, cx: &mut VisualTestContext| {
20443            project.update(cx, |project, cx| {
20444                let current = project
20445                    .lsp_store()
20446                    .read(cx)
20447                    .as_local()
20448                    .unwrap()
20449                    .language_servers
20450                    .len();
20451                assert_eq!(expected, current, "{context}");
20452            });
20453        };
20454
20455    assert_language_servers_count(
20456        0,
20457        "No servers should be running before any file is open",
20458        cx,
20459    );
20460    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20461    let main_editor = workspace
20462        .update_in(cx, |workspace, window, cx| {
20463            workspace.open_path(
20464                (worktree_id, "main.rs"),
20465                Some(pane.downgrade()),
20466                true,
20467                window,
20468                cx,
20469            )
20470        })
20471        .unwrap()
20472        .await
20473        .downcast::<Editor>()
20474        .unwrap();
20475    pane.update(cx, |pane, cx| {
20476        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20477        open_editor.update(cx, |editor, cx| {
20478            assert_eq!(
20479                editor.display_text(cx),
20480                "fn main() {}",
20481                "Original main.rs text on initial open",
20482            );
20483        });
20484        assert_eq!(open_editor, main_editor);
20485    });
20486    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
20487
20488    let external_editor = workspace
20489        .update_in(cx, |workspace, window, cx| {
20490            workspace.open_abs_path(
20491                PathBuf::from("/root/foo/bar/external_file.rs"),
20492                OpenOptions::default(),
20493                window,
20494                cx,
20495            )
20496        })
20497        .await
20498        .expect("opening external file")
20499        .downcast::<Editor>()
20500        .expect("downcasted external file's open element to editor");
20501    pane.update(cx, |pane, cx| {
20502        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20503        open_editor.update(cx, |editor, cx| {
20504            assert_eq!(
20505                editor.display_text(cx),
20506                "pub mod external {}",
20507                "External file is open now",
20508            );
20509        });
20510        assert_eq!(open_editor, external_editor);
20511    });
20512    assert_language_servers_count(
20513        1,
20514        "Second, external, *.rs file should join the existing server",
20515        cx,
20516    );
20517
20518    pane.update_in(cx, |pane, window, cx| {
20519        pane.close_active_item(&CloseActiveItem::default(), window, cx)
20520    })
20521    .unwrap()
20522    .await
20523    .unwrap();
20524    pane.update_in(cx, |pane, window, cx| {
20525        pane.navigate_backward(window, cx);
20526    });
20527    cx.run_until_parked();
20528    pane.update(cx, |pane, cx| {
20529        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20530        open_editor.update(cx, |editor, cx| {
20531            assert_eq!(
20532                editor.display_text(cx),
20533                "pub mod external {}",
20534                "External file is open now",
20535            );
20536        });
20537    });
20538    assert_language_servers_count(
20539        1,
20540        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
20541        cx,
20542    );
20543
20544    cx.update(|_, cx| {
20545        workspace::reload(&workspace::Reload::default(), cx);
20546    });
20547    assert_language_servers_count(
20548        1,
20549        "After reloading the worktree with local and external files opened, only one project should be started",
20550        cx,
20551    );
20552}
20553
20554#[gpui::test]
20555async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
20556    init_test(cx, |_| {});
20557
20558    let mut cx = EditorTestContext::new(cx).await;
20559    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20560    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20561
20562    // test cursor move to start of each line on tab
20563    // for `if`, `elif`, `else`, `while`, `with` and `for`
20564    cx.set_state(indoc! {"
20565        def main():
20566        ˇ    for item in items:
20567        ˇ        while item.active:
20568        ˇ            if item.value > 10:
20569        ˇ                continue
20570        ˇ            elif item.value < 0:
20571        ˇ                break
20572        ˇ            else:
20573        ˇ                with item.context() as ctx:
20574        ˇ                    yield count
20575        ˇ        else:
20576        ˇ            log('while else')
20577        ˇ    else:
20578        ˇ        log('for else')
20579    "});
20580    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20581    cx.assert_editor_state(indoc! {"
20582        def main():
20583            ˇfor item in items:
20584                ˇwhile item.active:
20585                    ˇif item.value > 10:
20586                        ˇcontinue
20587                    ˇelif item.value < 0:
20588                        ˇbreak
20589                    ˇelse:
20590                        ˇwith item.context() as ctx:
20591                            ˇyield count
20592                ˇelse:
20593                    ˇlog('while else')
20594            ˇelse:
20595                ˇlog('for else')
20596    "});
20597    // test relative indent is preserved when tab
20598    // for `if`, `elif`, `else`, `while`, `with` and `for`
20599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20600    cx.assert_editor_state(indoc! {"
20601        def main():
20602                ˇfor item in items:
20603                    ˇwhile item.active:
20604                        ˇif item.value > 10:
20605                            ˇcontinue
20606                        ˇelif item.value < 0:
20607                            ˇbreak
20608                        ˇelse:
20609                            ˇwith item.context() as ctx:
20610                                ˇyield count
20611                    ˇelse:
20612                        ˇlog('while else')
20613                ˇelse:
20614                    ˇlog('for else')
20615    "});
20616
20617    // test cursor move to start of each line on tab
20618    // for `try`, `except`, `else`, `finally`, `match` and `def`
20619    cx.set_state(indoc! {"
20620        def main():
20621        ˇ    try:
20622        ˇ       fetch()
20623        ˇ    except ValueError:
20624        ˇ       handle_error()
20625        ˇ    else:
20626        ˇ        match value:
20627        ˇ            case _:
20628        ˇ    finally:
20629        ˇ        def status():
20630        ˇ            return 0
20631    "});
20632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20633    cx.assert_editor_state(indoc! {"
20634        def main():
20635            ˇtry:
20636                ˇfetch()
20637            ˇexcept ValueError:
20638                ˇhandle_error()
20639            ˇelse:
20640                ˇmatch value:
20641                    ˇcase _:
20642            ˇfinally:
20643                ˇdef status():
20644                    ˇreturn 0
20645    "});
20646    // test relative indent is preserved when tab
20647    // for `try`, `except`, `else`, `finally`, `match` and `def`
20648    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
20649    cx.assert_editor_state(indoc! {"
20650        def main():
20651                ˇtry:
20652                    ˇfetch()
20653                ˇexcept ValueError:
20654                    ˇhandle_error()
20655                ˇelse:
20656                    ˇmatch value:
20657                        ˇcase _:
20658                ˇfinally:
20659                    ˇdef status():
20660                        ˇreturn 0
20661    "});
20662}
20663
20664#[gpui::test]
20665async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
20666    init_test(cx, |_| {});
20667
20668    let mut cx = EditorTestContext::new(cx).await;
20669    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20671
20672    // test `else` auto outdents when typed inside `if` block
20673    cx.set_state(indoc! {"
20674        def main():
20675            if i == 2:
20676                return
20677                ˇ
20678    "});
20679    cx.update_editor(|editor, window, cx| {
20680        editor.handle_input("else:", window, cx);
20681    });
20682    cx.assert_editor_state(indoc! {"
20683        def main():
20684            if i == 2:
20685                return
20686            else:ˇ
20687    "});
20688
20689    // test `except` auto outdents when typed inside `try` block
20690    cx.set_state(indoc! {"
20691        def main():
20692            try:
20693                i = 2
20694                ˇ
20695    "});
20696    cx.update_editor(|editor, window, cx| {
20697        editor.handle_input("except:", window, cx);
20698    });
20699    cx.assert_editor_state(indoc! {"
20700        def main():
20701            try:
20702                i = 2
20703            except:ˇ
20704    "});
20705
20706    // test `else` auto outdents when typed inside `except` block
20707    cx.set_state(indoc! {"
20708        def main():
20709            try:
20710                i = 2
20711            except:
20712                j = 2
20713                ˇ
20714    "});
20715    cx.update_editor(|editor, window, cx| {
20716        editor.handle_input("else:", window, cx);
20717    });
20718    cx.assert_editor_state(indoc! {"
20719        def main():
20720            try:
20721                i = 2
20722            except:
20723                j = 2
20724            else:ˇ
20725    "});
20726
20727    // test `finally` auto outdents when typed inside `else` block
20728    cx.set_state(indoc! {"
20729        def main():
20730            try:
20731                i = 2
20732            except:
20733                j = 2
20734            else:
20735                k = 2
20736                ˇ
20737    "});
20738    cx.update_editor(|editor, window, cx| {
20739        editor.handle_input("finally:", window, cx);
20740    });
20741    cx.assert_editor_state(indoc! {"
20742        def main():
20743            try:
20744                i = 2
20745            except:
20746                j = 2
20747            else:
20748                k = 2
20749            finally:ˇ
20750    "});
20751
20752    // TODO: test `except` auto outdents when typed inside `try` block right after for block
20753    // cx.set_state(indoc! {"
20754    //     def main():
20755    //         try:
20756    //             for i in range(n):
20757    //                 pass
20758    //             ˇ
20759    // "});
20760    // cx.update_editor(|editor, window, cx| {
20761    //     editor.handle_input("except:", window, cx);
20762    // });
20763    // cx.assert_editor_state(indoc! {"
20764    //     def main():
20765    //         try:
20766    //             for i in range(n):
20767    //                 pass
20768    //         except:ˇ
20769    // "});
20770
20771    // TODO: test `else` auto outdents when typed inside `except` block right after for block
20772    // cx.set_state(indoc! {"
20773    //     def main():
20774    //         try:
20775    //             i = 2
20776    //         except:
20777    //             for i in range(n):
20778    //                 pass
20779    //             ˇ
20780    // "});
20781    // cx.update_editor(|editor, window, cx| {
20782    //     editor.handle_input("else:", window, cx);
20783    // });
20784    // cx.assert_editor_state(indoc! {"
20785    //     def main():
20786    //         try:
20787    //             i = 2
20788    //         except:
20789    //             for i in range(n):
20790    //                 pass
20791    //         else:ˇ
20792    // "});
20793
20794    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
20795    // cx.set_state(indoc! {"
20796    //     def main():
20797    //         try:
20798    //             i = 2
20799    //         except:
20800    //             j = 2
20801    //         else:
20802    //             for i in range(n):
20803    //                 pass
20804    //             ˇ
20805    // "});
20806    // cx.update_editor(|editor, window, cx| {
20807    //     editor.handle_input("finally:", window, cx);
20808    // });
20809    // cx.assert_editor_state(indoc! {"
20810    //     def main():
20811    //         try:
20812    //             i = 2
20813    //         except:
20814    //             j = 2
20815    //         else:
20816    //             for i in range(n):
20817    //                 pass
20818    //         finally:ˇ
20819    // "});
20820
20821    // test `else` stays at correct indent when typed after `for` block
20822    cx.set_state(indoc! {"
20823        def main():
20824            for i in range(10):
20825                if i == 3:
20826                    break
20827            ˇ
20828    "});
20829    cx.update_editor(|editor, window, cx| {
20830        editor.handle_input("else:", window, cx);
20831    });
20832    cx.assert_editor_state(indoc! {"
20833        def main():
20834            for i in range(10):
20835                if i == 3:
20836                    break
20837            else:ˇ
20838    "});
20839
20840    // test does not outdent on typing after line with square brackets
20841    cx.set_state(indoc! {"
20842        def f() -> list[str]:
20843            ˇ
20844    "});
20845    cx.update_editor(|editor, window, cx| {
20846        editor.handle_input("a", window, cx);
20847    });
20848    cx.assert_editor_state(indoc! {"
20849        def f() -> list[str]:
2085020851    "});
20852}
20853
20854#[gpui::test]
20855async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
20856    init_test(cx, |_| {});
20857    update_test_language_settings(cx, |settings| {
20858        settings.defaults.extend_comment_on_newline = Some(false);
20859    });
20860    let mut cx = EditorTestContext::new(cx).await;
20861    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
20862    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20863
20864    // test correct indent after newline on comment
20865    cx.set_state(indoc! {"
20866        # COMMENT:ˇ
20867    "});
20868    cx.update_editor(|editor, window, cx| {
20869        editor.newline(&Newline, window, cx);
20870    });
20871    cx.assert_editor_state(indoc! {"
20872        # COMMENT:
20873        ˇ
20874    "});
20875
20876    // test correct indent after newline in brackets
20877    cx.set_state(indoc! {"
20878        {ˇ}
20879    "});
20880    cx.update_editor(|editor, window, cx| {
20881        editor.newline(&Newline, window, cx);
20882    });
20883    cx.run_until_parked();
20884    cx.assert_editor_state(indoc! {"
20885        {
20886            ˇ
20887        }
20888    "});
20889
20890    cx.set_state(indoc! {"
20891        (ˇ)
20892    "});
20893    cx.update_editor(|editor, window, cx| {
20894        editor.newline(&Newline, window, cx);
20895    });
20896    cx.run_until_parked();
20897    cx.assert_editor_state(indoc! {"
20898        (
20899            ˇ
20900        )
20901    "});
20902
20903    // do not indent after empty lists or dictionaries
20904    cx.set_state(indoc! {"
20905        a = []ˇ
20906    "});
20907    cx.update_editor(|editor, window, cx| {
20908        editor.newline(&Newline, window, cx);
20909    });
20910    cx.run_until_parked();
20911    cx.assert_editor_state(indoc! {"
20912        a = []
20913        ˇ
20914    "});
20915}
20916
20917fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
20918    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
20919    point..point
20920}
20921
20922fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
20923    let (text, ranges) = marked_text_ranges(marked_text, true);
20924    assert_eq!(editor.text(cx), text);
20925    assert_eq!(
20926        editor.selections.ranges(cx),
20927        ranges,
20928        "Assert selections are {}",
20929        marked_text
20930    );
20931}
20932
20933pub fn handle_signature_help_request(
20934    cx: &mut EditorLspTestContext,
20935    mocked_response: lsp::SignatureHelp,
20936) -> impl Future<Output = ()> + use<> {
20937    let mut request =
20938        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
20939            let mocked_response = mocked_response.clone();
20940            async move { Ok(Some(mocked_response)) }
20941        });
20942
20943    async move {
20944        request.next().await;
20945    }
20946}
20947
20948/// Handle completion request passing a marked string specifying where the completion
20949/// should be triggered from using '|' character, what range should be replaced, and what completions
20950/// should be returned using '<' and '>' to delimit the range.
20951///
20952/// Also see `handle_completion_request_with_insert_and_replace`.
20953#[track_caller]
20954pub fn handle_completion_request(
20955    cx: &mut EditorLspTestContext,
20956    marked_string: &str,
20957    completions: Vec<&'static str>,
20958    counter: Arc<AtomicUsize>,
20959) -> impl Future<Output = ()> {
20960    let complete_from_marker: TextRangeMarker = '|'.into();
20961    let replace_range_marker: TextRangeMarker = ('<', '>').into();
20962    let (_, mut marked_ranges) = marked_text_ranges_by(
20963        marked_string,
20964        vec![complete_from_marker.clone(), replace_range_marker.clone()],
20965    );
20966
20967    let complete_from_position =
20968        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
20969    let replace_range =
20970        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
20971
20972    let mut request =
20973        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
20974            let completions = completions.clone();
20975            counter.fetch_add(1, atomic::Ordering::Release);
20976            async move {
20977                assert_eq!(params.text_document_position.text_document.uri, url.clone());
20978                assert_eq!(
20979                    params.text_document_position.position,
20980                    complete_from_position
20981                );
20982                Ok(Some(lsp::CompletionResponse::Array(
20983                    completions
20984                        .iter()
20985                        .map(|completion_text| lsp::CompletionItem {
20986                            label: completion_text.to_string(),
20987                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20988                                range: replace_range,
20989                                new_text: completion_text.to_string(),
20990                            })),
20991                            ..Default::default()
20992                        })
20993                        .collect(),
20994                )))
20995            }
20996        });
20997
20998    async move {
20999        request.next().await;
21000    }
21001}
21002
21003/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21004/// given instead, which also contains an `insert` range.
21005///
21006/// This function uses the cursor position to mimic what Rust-Analyzer provides as the `insert` range,
21007/// that is, `replace_range.start..cursor_pos`.
21008pub fn handle_completion_request_with_insert_and_replace(
21009    cx: &mut EditorLspTestContext,
21010    marked_string: &str,
21011    completions: Vec<&'static str>,
21012    counter: Arc<AtomicUsize>,
21013) -> impl Future<Output = ()> {
21014    let complete_from_marker: TextRangeMarker = '|'.into();
21015    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21016    let (_, mut marked_ranges) = marked_text_ranges_by(
21017        marked_string,
21018        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21019    );
21020
21021    let complete_from_position =
21022        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21023    let replace_range =
21024        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21025
21026    let mut request =
21027        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21028            let completions = completions.clone();
21029            counter.fetch_add(1, atomic::Ordering::Release);
21030            async move {
21031                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21032                assert_eq!(
21033                    params.text_document_position.position, complete_from_position,
21034                    "marker `|` position doesn't match",
21035                );
21036                Ok(Some(lsp::CompletionResponse::Array(
21037                    completions
21038                        .iter()
21039                        .map(|completion_text| lsp::CompletionItem {
21040                            label: completion_text.to_string(),
21041                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21042                                lsp::InsertReplaceEdit {
21043                                    insert: lsp::Range {
21044                                        start: replace_range.start,
21045                                        end: complete_from_position,
21046                                    },
21047                                    replace: replace_range,
21048                                    new_text: completion_text.to_string(),
21049                                },
21050                            )),
21051                            ..Default::default()
21052                        })
21053                        .collect(),
21054                )))
21055            }
21056        });
21057
21058    async move {
21059        request.next().await;
21060    }
21061}
21062
21063fn handle_resolve_completion_request(
21064    cx: &mut EditorLspTestContext,
21065    edits: Option<Vec<(&'static str, &'static str)>>,
21066) -> impl Future<Output = ()> {
21067    let edits = edits.map(|edits| {
21068        edits
21069            .iter()
21070            .map(|(marked_string, new_text)| {
21071                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21072                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21073                lsp::TextEdit::new(replace_range, new_text.to_string())
21074            })
21075            .collect::<Vec<_>>()
21076    });
21077
21078    let mut request =
21079        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21080            let edits = edits.clone();
21081            async move {
21082                Ok(lsp::CompletionItem {
21083                    additional_text_edits: edits,
21084                    ..Default::default()
21085                })
21086            }
21087        });
21088
21089    async move {
21090        request.next().await;
21091    }
21092}
21093
21094pub(crate) fn update_test_language_settings(
21095    cx: &mut TestAppContext,
21096    f: impl Fn(&mut AllLanguageSettingsContent),
21097) {
21098    cx.update(|cx| {
21099        SettingsStore::update_global(cx, |store, cx| {
21100            store.update_user_settings::<AllLanguageSettings>(cx, f);
21101        });
21102    });
21103}
21104
21105pub(crate) fn update_test_project_settings(
21106    cx: &mut TestAppContext,
21107    f: impl Fn(&mut ProjectSettings),
21108) {
21109    cx.update(|cx| {
21110        SettingsStore::update_global(cx, |store, cx| {
21111            store.update_user_settings::<ProjectSettings>(cx, f);
21112        });
21113    });
21114}
21115
21116pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
21117    cx.update(|cx| {
21118        assets::Assets.load_test_fonts(cx);
21119        let store = SettingsStore::test(cx);
21120        cx.set_global(store);
21121        theme::init(theme::LoadThemes::JustBase, cx);
21122        release_channel::init(SemanticVersion::default(), cx);
21123        client::init_settings(cx);
21124        language::init(cx);
21125        Project::init_settings(cx);
21126        workspace::init_settings(cx);
21127        crate::init(cx);
21128    });
21129
21130    update_test_language_settings(cx, f);
21131}
21132
21133#[track_caller]
21134fn assert_hunk_revert(
21135    not_reverted_text_with_selections: &str,
21136    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
21137    expected_reverted_text_with_selections: &str,
21138    base_text: &str,
21139    cx: &mut EditorLspTestContext,
21140) {
21141    cx.set_state(not_reverted_text_with_selections);
21142    cx.set_head_text(base_text);
21143    cx.executor().run_until_parked();
21144
21145    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
21146        let snapshot = editor.snapshot(window, cx);
21147        let reverted_hunk_statuses = snapshot
21148            .buffer_snapshot
21149            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
21150            .map(|hunk| hunk.status().kind)
21151            .collect::<Vec<_>>();
21152
21153        editor.git_restore(&Default::default(), window, cx);
21154        reverted_hunk_statuses
21155    });
21156    cx.executor().run_until_parked();
21157    cx.assert_editor_state(expected_reverted_text_with_selections);
21158    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
21159}