editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    invalid_buffer_view::InvalidBufferView,
   61    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   62    register_project_item,
   63};
   64
   65#[gpui::test]
   66fn test_edit_events(cx: &mut TestAppContext) {
   67    init_test(cx, |_| {});
   68
   69    let buffer = cx.new(|cx| {
   70        let mut buffer = language::Buffer::local("123456", cx);
   71        buffer.set_group_interval(Duration::from_secs(1));
   72        buffer
   73    });
   74
   75    let events = Rc::new(RefCell::new(Vec::new()));
   76    let editor1 = cx.add_window({
   77        let events = events.clone();
   78        |window, cx| {
   79            let entity = cx.entity();
   80            cx.subscribe_in(
   81                &entity,
   82                window,
   83                move |_, _, event: &EditorEvent, _, _| match event {
   84                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   85                    EditorEvent::BufferEdited => {
   86                        events.borrow_mut().push(("editor1", "buffer edited"))
   87                    }
   88                    _ => {}
   89                },
   90            )
   91            .detach();
   92            Editor::for_buffer(buffer.clone(), None, window, cx)
   93        }
   94    });
   95
   96    let editor2 = cx.add_window({
   97        let events = events.clone();
   98        |window, cx| {
   99            cx.subscribe_in(
  100                &cx.entity(),
  101                window,
  102                move |_, _, event: &EditorEvent, _, _| match event {
  103                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  104                    EditorEvent::BufferEdited => {
  105                        events.borrow_mut().push(("editor2", "buffer edited"))
  106                    }
  107                    _ => {}
  108                },
  109            )
  110            .detach();
  111            Editor::for_buffer(buffer.clone(), None, window, cx)
  112        }
  113    });
  114
  115    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  116
  117    // Mutating editor 1 will emit an `Edited` event only for that editor.
  118    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  119    assert_eq!(
  120        mem::take(&mut *events.borrow_mut()),
  121        [
  122            ("editor1", "edited"),
  123            ("editor1", "buffer edited"),
  124            ("editor2", "buffer edited"),
  125        ]
  126    );
  127
  128    // Mutating editor 2 will emit an `Edited` event only for that editor.
  129    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  130    assert_eq!(
  131        mem::take(&mut *events.borrow_mut()),
  132        [
  133            ("editor2", "edited"),
  134            ("editor1", "buffer edited"),
  135            ("editor2", "buffer edited"),
  136        ]
  137    );
  138
  139    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  140    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  141    assert_eq!(
  142        mem::take(&mut *events.borrow_mut()),
  143        [
  144            ("editor1", "edited"),
  145            ("editor1", "buffer edited"),
  146            ("editor2", "buffer edited"),
  147        ]
  148    );
  149
  150    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  151    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  152    assert_eq!(
  153        mem::take(&mut *events.borrow_mut()),
  154        [
  155            ("editor1", "edited"),
  156            ("editor1", "buffer edited"),
  157            ("editor2", "buffer edited"),
  158        ]
  159    );
  160
  161    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  162    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  163    assert_eq!(
  164        mem::take(&mut *events.borrow_mut()),
  165        [
  166            ("editor2", "edited"),
  167            ("editor1", "buffer edited"),
  168            ("editor2", "buffer edited"),
  169        ]
  170    );
  171
  172    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  173    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  174    assert_eq!(
  175        mem::take(&mut *events.borrow_mut()),
  176        [
  177            ("editor2", "edited"),
  178            ("editor1", "buffer edited"),
  179            ("editor2", "buffer edited"),
  180        ]
  181    );
  182
  183    // No event is emitted when the mutation is a no-op.
  184    _ = editor2.update(cx, |editor, window, cx| {
  185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  186            s.select_ranges([0..0])
  187        });
  188
  189        editor.backspace(&Backspace, window, cx);
  190    });
  191    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  192}
  193
  194#[gpui::test]
  195fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  196    init_test(cx, |_| {});
  197
  198    let mut now = Instant::now();
  199    let group_interval = Duration::from_millis(1);
  200    let buffer = cx.new(|cx| {
  201        let mut buf = language::Buffer::local("123456", cx);
  202        buf.set_group_interval(group_interval);
  203        buf
  204    });
  205    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  206    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  207
  208    _ = editor.update(cx, |editor, window, cx| {
  209        editor.start_transaction_at(now, window, cx);
  210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  211            s.select_ranges([2..4])
  212        });
  213
  214        editor.insert("cd", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cd56");
  217        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  218
  219        editor.start_transaction_at(now, window, cx);
  220        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  221            s.select_ranges([4..5])
  222        });
  223        editor.insert("e", window, cx);
  224        editor.end_transaction_at(now, cx);
  225        assert_eq!(editor.text(cx), "12cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  227
  228        now += group_interval + Duration::from_millis(1);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([2..2])
  231        });
  232
  233        // Simulate an edit in another editor
  234        buffer.update(cx, |buffer, cx| {
  235            buffer.start_transaction_at(now, cx);
  236            buffer.edit([(0..1, "a")], None, cx);
  237            buffer.edit([(1..1, "b")], None, cx);
  238            buffer.end_transaction_at(now, cx);
  239        });
  240
  241        assert_eq!(editor.text(cx), "ab2cde6");
  242        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  243
  244        // Last transaction happened past the group interval in a different editor.
  245        // Undo it individually and don't restore selections.
  246        editor.undo(&Undo, window, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  249
  250        // First two transactions happened within the group interval in this editor.
  251        // Undo them together and restore selections.
  252        editor.undo(&Undo, window, cx);
  253        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  254        assert_eq!(editor.text(cx), "123456");
  255        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  256
  257        // Redo the first two transactions together.
  258        editor.redo(&Redo, window, cx);
  259        assert_eq!(editor.text(cx), "12cde6");
  260        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  261
  262        // Redo the last transaction on its own.
  263        editor.redo(&Redo, window, cx);
  264        assert_eq!(editor.text(cx), "ab2cde6");
  265        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  266
  267        // Test empty transactions.
  268        editor.start_transaction_at(now, window, cx);
  269        editor.end_transaction_at(now, cx);
  270        editor.undo(&Undo, window, cx);
  271        assert_eq!(editor.text(cx), "12cde6");
  272    });
  273}
  274
  275#[gpui::test]
  276fn test_ime_composition(cx: &mut TestAppContext) {
  277    init_test(cx, |_| {});
  278
  279    let buffer = cx.new(|cx| {
  280        let mut buffer = language::Buffer::local("abcde", cx);
  281        // Ensure automatic grouping doesn't occur.
  282        buffer.set_group_interval(Duration::ZERO);
  283        buffer
  284    });
  285
  286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  287    cx.add_window(|window, cx| {
  288        let mut editor = build_editor(buffer.clone(), window, cx);
  289
  290        // Start a new IME composition.
  291        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  292        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  293        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  294        assert_eq!(editor.text(cx), "äbcde");
  295        assert_eq!(
  296            editor.marked_text_ranges(cx),
  297            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  298        );
  299
  300        // Finalize IME composition.
  301        editor.replace_text_in_range(None, "ā", window, cx);
  302        assert_eq!(editor.text(cx), "ābcde");
  303        assert_eq!(editor.marked_text_ranges(cx), None);
  304
  305        // IME composition edits are grouped and are undone/redone at once.
  306        editor.undo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "abcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309        editor.redo(&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.
  314        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  315        assert_eq!(
  316            editor.marked_text_ranges(cx),
  317            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  318        );
  319
  320        // Undoing during an IME composition cancels it.
  321        editor.undo(&Default::default(), window, cx);
  322        assert_eq!(editor.text(cx), "ābcde");
  323        assert_eq!(editor.marked_text_ranges(cx), None);
  324
  325        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  326        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  327        assert_eq!(editor.text(cx), "ābcdè");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  331        );
  332
  333        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  334        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  335        assert_eq!(editor.text(cx), "ābcdę");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // Start a new IME composition with multiple cursors.
  339        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  340            s.select_ranges([
  341                OffsetUtf16(1)..OffsetUtf16(1),
  342                OffsetUtf16(3)..OffsetUtf16(3),
  343                OffsetUtf16(5)..OffsetUtf16(5),
  344            ])
  345        });
  346        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  347        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![
  351                OffsetUtf16(0)..OffsetUtf16(3),
  352                OffsetUtf16(4)..OffsetUtf16(7),
  353                OffsetUtf16(8)..OffsetUtf16(11)
  354            ])
  355        );
  356
  357        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  358        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  359        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  360        assert_eq!(
  361            editor.marked_text_ranges(cx),
  362            Some(vec![
  363                OffsetUtf16(1)..OffsetUtf16(2),
  364                OffsetUtf16(5)..OffsetUtf16(6),
  365                OffsetUtf16(9)..OffsetUtf16(10)
  366            ])
  367        );
  368
  369        // Finalize IME composition with multiple cursors.
  370        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  371        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        editor
  375    });
  376}
  377
  378#[gpui::test]
  379fn test_selection_with_mouse(cx: &mut TestAppContext) {
  380    init_test(cx, |_| {});
  381
  382    let editor = cx.add_window(|window, cx| {
  383        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  384        build_editor(buffer, window, cx)
  385    });
  386
  387    _ = editor.update(cx, |editor, window, cx| {
  388        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  389    });
  390    assert_eq!(
  391        editor
  392            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  393            .unwrap(),
  394        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  395    );
  396
  397    _ = editor.update(cx, |editor, window, cx| {
  398        editor.update_selection(
  399            DisplayPoint::new(DisplayRow(3), 3),
  400            0,
  401            gpui::Point::<f32>::default(),
  402            window,
  403            cx,
  404        );
  405    });
  406
  407    assert_eq!(
  408        editor
  409            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  410            .unwrap(),
  411        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  412    );
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.update_selection(
  416            DisplayPoint::new(DisplayRow(1), 1),
  417            0,
  418            gpui::Point::<f32>::default(),
  419            window,
  420            cx,
  421        );
  422    });
  423
  424    assert_eq!(
  425        editor
  426            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  427            .unwrap(),
  428        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  429    );
  430
  431    _ = editor.update(cx, |editor, window, cx| {
  432        editor.end_selection(window, cx);
  433        editor.update_selection(
  434            DisplayPoint::new(DisplayRow(3), 3),
  435            0,
  436            gpui::Point::<f32>::default(),
  437            window,
  438            cx,
  439        );
  440    });
  441
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(0), 0),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  463            .unwrap(),
  464        [
  465            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  466            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  467        ]
  468    );
  469
  470    _ = editor.update(cx, |editor, window, cx| {
  471        editor.end_selection(window, cx);
  472    });
  473
  474    assert_eq!(
  475        editor
  476            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  477            .unwrap(),
  478        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  479    );
  480}
  481
  482#[gpui::test]
  483fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  484    init_test(cx, |_| {});
  485
  486    let editor = cx.add_window(|window, cx| {
  487        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  488        build_editor(buffer, window, cx)
  489    });
  490
  491    _ = editor.update(cx, |editor, window, cx| {
  492        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  493    });
  494
  495    _ = editor.update(cx, |editor, window, cx| {
  496        editor.end_selection(window, cx);
  497    });
  498
  499    _ = editor.update(cx, |editor, window, cx| {
  500        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  501    });
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  510            .unwrap(),
  511        [
  512            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  513            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  514        ]
  515    );
  516
  517    _ = editor.update(cx, |editor, window, cx| {
  518        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  519    });
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  544        assert_eq!(
  545            editor.selections.display_ranges(cx),
  546            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  547        );
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.update_selection(
  552            DisplayPoint::new(DisplayRow(3), 3),
  553            0,
  554            gpui::Point::<f32>::default(),
  555            window,
  556            cx,
  557        );
  558        assert_eq!(
  559            editor.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  561        );
  562    });
  563
  564    _ = editor.update(cx, |editor, window, cx| {
  565        editor.cancel(&Cancel, window, cx);
  566        editor.update_selection(
  567            DisplayPoint::new(DisplayRow(1), 1),
  568            0,
  569            gpui::Point::<f32>::default(),
  570            window,
  571            cx,
  572        );
  573        assert_eq!(
  574            editor.selections.display_ranges(cx),
  575            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  576        );
  577    });
  578}
  579
  580#[gpui::test]
  581fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  582    init_test(cx, |_| {});
  583
  584    let editor = cx.add_window(|window, cx| {
  585        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  586        build_editor(buffer, window, cx)
  587    });
  588
  589    _ = editor.update(cx, |editor, window, cx| {
  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_down(&Default::default(), window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  600        );
  601
  602        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  606        );
  607
  608        editor.move_up(&Default::default(), window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  612        );
  613    });
  614}
  615
  616#[gpui::test]
  617fn test_clone(cx: &mut TestAppContext) {
  618    init_test(cx, |_| {});
  619
  620    let (text, selection_ranges) = marked_text_ranges(
  621        indoc! {"
  622            one
  623            two
  624            threeˇ
  625            four
  626            fiveˇ
  627        "},
  628        true,
  629    );
  630
  631    let editor = cx.add_window(|window, cx| {
  632        let buffer = MultiBuffer::build_simple(&text, cx);
  633        build_editor(buffer, window, cx)
  634    });
  635
  636    _ = editor.update(cx, |editor, window, cx| {
  637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  638            s.select_ranges(selection_ranges.clone())
  639        });
  640        editor.fold_creases(
  641            vec![
  642                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  643                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  644            ],
  645            true,
  646            window,
  647            cx,
  648        );
  649    });
  650
  651    let cloned_editor = editor
  652        .update(cx, |editor, _, cx| {
  653            cx.open_window(Default::default(), |window, cx| {
  654                cx.new(|cx| editor.clone(window, cx))
  655            })
  656        })
  657        .unwrap()
  658        .unwrap();
  659
  660    let snapshot = editor
  661        .update(cx, |e, window, cx| e.snapshot(window, cx))
  662        .unwrap();
  663    let cloned_snapshot = cloned_editor
  664        .update(cx, |e, window, cx| e.snapshot(window, cx))
  665        .unwrap();
  666
  667    assert_eq!(
  668        cloned_editor
  669            .update(cx, |e, _, cx| e.display_text(cx))
  670            .unwrap(),
  671        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  672    );
  673    assert_eq!(
  674        cloned_snapshot
  675            .folds_in_range(0..text.len())
  676            .collect::<Vec<_>>(),
  677        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  678    );
  679    assert_set_eq!(
  680        cloned_editor
  681            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  682            .unwrap(),
  683        editor
  684            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  685            .unwrap()
  686    );
  687    assert_set_eq!(
  688        cloned_editor
  689            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  690            .unwrap(),
  691        editor
  692            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  693            .unwrap()
  694    );
  695}
  696
  697#[gpui::test]
  698async fn test_navigation_history(cx: &mut TestAppContext) {
  699    init_test(cx, |_| {});
  700
  701    use workspace::item::Item;
  702
  703    let fs = FakeFs::new(cx.executor());
  704    let project = Project::test(fs, [], cx).await;
  705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  706    let pane = workspace
  707        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  708        .unwrap();
  709
  710    _ = workspace.update(cx, |_v, window, cx| {
  711        cx.new(|cx| {
  712            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  713            let mut editor = build_editor(buffer, window, cx);
  714            let handle = cx.entity();
  715            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  716
  717            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  718                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  719            }
  720
  721            // Move the cursor a small distance.
  722            // Nothing is added to the navigation history.
  723            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  724                s.select_display_ranges([
  725                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  726                ])
  727            });
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  731                ])
  732            });
  733            assert!(pop_history(&mut editor, cx).is_none());
  734
  735            // Move the cursor a large distance.
  736            // The history can jump back to the previous position.
  737            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  738                s.select_display_ranges([
  739                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  740                ])
  741            });
  742            let nav_entry = pop_history(&mut editor, cx).unwrap();
  743            editor.navigate(nav_entry.data.unwrap(), window, cx);
  744            assert_eq!(nav_entry.item.id(), cx.entity_id());
  745            assert_eq!(
  746                editor.selections.display_ranges(cx),
  747                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  748            );
  749            assert!(pop_history(&mut editor, cx).is_none());
  750
  751            // Move the cursor a small distance via the mouse.
  752            // Nothing is added to the navigation history.
  753            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  754            editor.end_selection(window, cx);
  755            assert_eq!(
  756                editor.selections.display_ranges(cx),
  757                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  758            );
  759            assert!(pop_history(&mut editor, cx).is_none());
  760
  761            // Move the cursor a large distance via the mouse.
  762            // The history can jump back to the previous position.
  763            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  764            editor.end_selection(window, cx);
  765            assert_eq!(
  766                editor.selections.display_ranges(cx),
  767                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  768            );
  769            let nav_entry = pop_history(&mut editor, cx).unwrap();
  770            editor.navigate(nav_entry.data.unwrap(), window, cx);
  771            assert_eq!(nav_entry.item.id(), cx.entity_id());
  772            assert_eq!(
  773                editor.selections.display_ranges(cx),
  774                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  775            );
  776            assert!(pop_history(&mut editor, cx).is_none());
  777
  778            // Set scroll position to check later
  779            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  780            let original_scroll_position = editor.scroll_manager.anchor();
  781
  782            // Jump to the end of the document and adjust scroll
  783            editor.move_to_end(&MoveToEnd, window, cx);
  784            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  785            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  786
  787            let nav_entry = pop_history(&mut editor, cx).unwrap();
  788            editor.navigate(nav_entry.data.unwrap(), window, cx);
  789            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  790
  791            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  792            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  793            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  794            let invalid_point = Point::new(9999, 0);
  795            editor.navigate(
  796                Box::new(NavigationData {
  797                    cursor_anchor: invalid_anchor,
  798                    cursor_position: invalid_point,
  799                    scroll_anchor: ScrollAnchor {
  800                        anchor: invalid_anchor,
  801                        offset: Default::default(),
  802                    },
  803                    scroll_top_row: invalid_point.row,
  804                }),
  805                window,
  806                cx,
  807            );
  808            assert_eq!(
  809                editor.selections.display_ranges(cx),
  810                &[editor.max_point(cx)..editor.max_point(cx)]
  811            );
  812            assert_eq!(
  813                editor.scroll_position(cx),
  814                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  815            );
  816
  817            editor
  818        })
  819    });
  820}
  821
  822#[gpui::test]
  823fn test_cancel(cx: &mut TestAppContext) {
  824    init_test(cx, |_| {});
  825
  826    let editor = cx.add_window(|window, cx| {
  827        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  828        build_editor(buffer, window, cx)
  829    });
  830
  831    _ = editor.update(cx, |editor, window, cx| {
  832        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  833        editor.update_selection(
  834            DisplayPoint::new(DisplayRow(1), 1),
  835            0,
  836            gpui::Point::<f32>::default(),
  837            window,
  838            cx,
  839        );
  840        editor.end_selection(window, cx);
  841
  842        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  843        editor.update_selection(
  844            DisplayPoint::new(DisplayRow(0), 3),
  845            0,
  846            gpui::Point::<f32>::default(),
  847            window,
  848            cx,
  849        );
  850        editor.end_selection(window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [
  854                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  855                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  856            ]
  857        );
  858    });
  859
  860    _ = editor.update(cx, |editor, window, cx| {
  861        editor.cancel(&Cancel, window, cx);
  862        assert_eq!(
  863            editor.selections.display_ranges(cx),
  864            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  865        );
  866    });
  867
  868    _ = editor.update(cx, |editor, window, cx| {
  869        editor.cancel(&Cancel, window, cx);
  870        assert_eq!(
  871            editor.selections.display_ranges(cx),
  872            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  873        );
  874    });
  875}
  876
  877#[gpui::test]
  878fn test_fold_action(cx: &mut TestAppContext) {
  879    init_test(cx, |_| {});
  880
  881    let editor = cx.add_window(|window, cx| {
  882        let buffer = MultiBuffer::build_simple(
  883            &"
  884                impl Foo {
  885                    // Hello!
  886
  887                    fn a() {
  888                        1
  889                    }
  890
  891                    fn b() {
  892                        2
  893                    }
  894
  895                    fn c() {
  896                        3
  897                    }
  898                }
  899            "
  900            .unindent(),
  901            cx,
  902        );
  903        build_editor(buffer, window, cx)
  904    });
  905
  906    _ = editor.update(cx, |editor, window, cx| {
  907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  908            s.select_display_ranges([
  909                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  910            ]);
  911        });
  912        editor.fold(&Fold, window, cx);
  913        assert_eq!(
  914            editor.display_text(cx),
  915            "
  916                impl Foo {
  917                    // Hello!
  918
  919                    fn a() {
  920                        1
  921                    }
  922
  923                    fn b() {⋯
  924                    }
  925
  926                    fn c() {⋯
  927                    }
  928                }
  929            "
  930            .unindent(),
  931        );
  932
  933        editor.fold(&Fold, window, cx);
  934        assert_eq!(
  935            editor.display_text(cx),
  936            "
  937                impl Foo {⋯
  938                }
  939            "
  940            .unindent(),
  941        );
  942
  943        editor.unfold_lines(&UnfoldLines, window, cx);
  944        assert_eq!(
  945            editor.display_text(cx),
  946            "
  947                impl Foo {
  948                    // Hello!
  949
  950                    fn a() {
  951                        1
  952                    }
  953
  954                    fn b() {⋯
  955                    }
  956
  957                    fn c() {⋯
  958                    }
  959                }
  960            "
  961            .unindent(),
  962        );
  963
  964        editor.unfold_lines(&UnfoldLines, window, cx);
  965        assert_eq!(
  966            editor.display_text(cx),
  967            editor.buffer.read(cx).read(cx).text()
  968        );
  969    });
  970}
  971
  972#[gpui::test]
  973fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  974    init_test(cx, |_| {});
  975
  976    let editor = cx.add_window(|window, cx| {
  977        let buffer = MultiBuffer::build_simple(
  978            &"
  979                class Foo:
  980                    # Hello!
  981
  982                    def a():
  983                        print(1)
  984
  985                    def b():
  986                        print(2)
  987
  988                    def c():
  989                        print(3)
  990            "
  991            .unindent(),
  992            cx,
  993        );
  994        build_editor(buffer, window, cx)
  995    });
  996
  997    _ = editor.update(cx, |editor, window, cx| {
  998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  999            s.select_display_ranges([
 1000                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1001            ]);
 1002        });
 1003        editor.fold(&Fold, window, cx);
 1004        assert_eq!(
 1005            editor.display_text(cx),
 1006            "
 1007                class Foo:
 1008                    # Hello!
 1009
 1010                    def a():
 1011                        print(1)
 1012
 1013                    def b():⋯
 1014
 1015                    def c():⋯
 1016            "
 1017            .unindent(),
 1018        );
 1019
 1020        editor.fold(&Fold, window, cx);
 1021        assert_eq!(
 1022            editor.display_text(cx),
 1023            "
 1024                class Foo:⋯
 1025            "
 1026            .unindent(),
 1027        );
 1028
 1029        editor.unfold_lines(&UnfoldLines, window, cx);
 1030        assert_eq!(
 1031            editor.display_text(cx),
 1032            "
 1033                class Foo:
 1034                    # Hello!
 1035
 1036                    def a():
 1037                        print(1)
 1038
 1039                    def b():⋯
 1040
 1041                    def c():⋯
 1042            "
 1043            .unindent(),
 1044        );
 1045
 1046        editor.unfold_lines(&UnfoldLines, window, cx);
 1047        assert_eq!(
 1048            editor.display_text(cx),
 1049            editor.buffer.read(cx).read(cx).text()
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                class Foo:
 1062                    # Hello!
 1063
 1064                    def a():
 1065                        print(1)
 1066
 1067                    def b():
 1068                        print(2)
 1069
 1070
 1071                    def c():
 1072                        print(3)
 1073
 1074
 1075            "
 1076            .unindent(),
 1077            cx,
 1078        );
 1079        build_editor(buffer, window, cx)
 1080    });
 1081
 1082    _ = editor.update(cx, |editor, window, cx| {
 1083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1084            s.select_display_ranges([
 1085                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1086            ]);
 1087        });
 1088        editor.fold(&Fold, window, cx);
 1089        assert_eq!(
 1090            editor.display_text(cx),
 1091            "
 1092                class Foo:
 1093                    # Hello!
 1094
 1095                    def a():
 1096                        print(1)
 1097
 1098                    def b():⋯
 1099
 1100
 1101                    def c():⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.fold(&Fold, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:⋯
 1113
 1114
 1115            "
 1116            .unindent(),
 1117        );
 1118
 1119        editor.unfold_lines(&UnfoldLines, window, cx);
 1120        assert_eq!(
 1121            editor.display_text(cx),
 1122            "
 1123                class Foo:
 1124                    # Hello!
 1125
 1126                    def a():
 1127                        print(1)
 1128
 1129                    def b():⋯
 1130
 1131
 1132                    def c():⋯
 1133
 1134
 1135            "
 1136            .unindent(),
 1137        );
 1138
 1139        editor.unfold_lines(&UnfoldLines, window, cx);
 1140        assert_eq!(
 1141            editor.display_text(cx),
 1142            editor.buffer.read(cx).read(cx).text()
 1143        );
 1144    });
 1145}
 1146
 1147#[gpui::test]
 1148fn test_fold_at_level(cx: &mut TestAppContext) {
 1149    init_test(cx, |_| {});
 1150
 1151    let editor = cx.add_window(|window, cx| {
 1152        let buffer = MultiBuffer::build_simple(
 1153            &"
 1154                class Foo:
 1155                    # Hello!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164                class Bar:
 1165                    # World!
 1166
 1167                    def a():
 1168                        print(1)
 1169
 1170                    def b():
 1171                        print(2)
 1172
 1173
 1174            "
 1175            .unindent(),
 1176            cx,
 1177        );
 1178        build_editor(buffer, window, cx)
 1179    });
 1180
 1181    _ = editor.update(cx, |editor, window, cx| {
 1182        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:
 1187                    # Hello!
 1188
 1189                    def a():⋯
 1190
 1191                    def b():⋯
 1192
 1193
 1194                class Bar:
 1195                    # World!
 1196
 1197                    def a():⋯
 1198
 1199                    def b():⋯
 1200
 1201
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:⋯
 1211
 1212
 1213                class Bar:⋯
 1214
 1215
 1216            "
 1217            .unindent(),
 1218        );
 1219
 1220        editor.unfold_all(&UnfoldAll, window, cx);
 1221        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1222        assert_eq!(
 1223            editor.display_text(cx),
 1224            "
 1225                class Foo:
 1226                    # Hello!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235                class Bar:
 1236                    # World!
 1237
 1238                    def a():
 1239                        print(1)
 1240
 1241                    def b():
 1242                        print(2)
 1243
 1244
 1245            "
 1246            .unindent(),
 1247        );
 1248
 1249        assert_eq!(
 1250            editor.display_text(cx),
 1251            editor.buffer.read(cx).read(cx).text()
 1252        );
 1253    });
 1254}
 1255
 1256#[gpui::test]
 1257fn test_move_cursor(cx: &mut TestAppContext) {
 1258    init_test(cx, |_| {});
 1259
 1260    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1261    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1262
 1263    buffer.update(cx, |buffer, cx| {
 1264        buffer.edit(
 1265            vec![
 1266                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1267                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1268            ],
 1269            None,
 1270            cx,
 1271        );
 1272    });
 1273    _ = editor.update(cx, |editor, window, cx| {
 1274        assert_eq!(
 1275            editor.selections.display_ranges(cx),
 1276            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1277        );
 1278
 1279        editor.move_down(&MoveDown, 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_right(&MoveRight, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1289        );
 1290
 1291        editor.move_left(&MoveLeft, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1295        );
 1296
 1297        editor.move_up(&MoveUp, 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.move_to_end(&MoveToEnd, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1307        );
 1308
 1309        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1316            s.select_display_ranges([
 1317                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1318            ]);
 1319        });
 1320        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1321        assert_eq!(
 1322            editor.selections.display_ranges(cx),
 1323            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1324        );
 1325
 1326        editor.select_to_end(&SelectToEnd, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1330        );
 1331    });
 1332}
 1333
 1334#[gpui::test]
 1335fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1336    init_test(cx, |_| {});
 1337
 1338    let editor = cx.add_window(|window, cx| {
 1339        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    assert_eq!('🟥'.len_utf8(), 4);
 1344    assert_eq!('α'.len_utf8(), 2);
 1345
 1346    _ = editor.update(cx, |editor, window, cx| {
 1347        editor.fold_creases(
 1348            vec![
 1349                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1350                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1351                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1352            ],
 1353            true,
 1354            window,
 1355            cx,
 1356        );
 1357        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1358
 1359        editor.move_right(&MoveRight, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[empty_range(0, "🟥".len())]
 1363        );
 1364        editor.move_right(&MoveRight, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(0, "🟥🟧".len())]
 1368        );
 1369        editor.move_right(&MoveRight, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(0, "🟥🟧⋯".len())]
 1373        );
 1374
 1375        editor.move_down(&MoveDown, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "ab⋯e".len())]
 1379        );
 1380        editor.move_left(&MoveLeft, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "ab⋯".len())]
 1384        );
 1385        editor.move_left(&MoveLeft, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(1, "ab".len())]
 1389        );
 1390        editor.move_left(&MoveLeft, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(1, "a".len())]
 1394        );
 1395
 1396        editor.move_down(&MoveDown, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "α".len())]
 1400        );
 1401        editor.move_right(&MoveRight, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "αβ".len())]
 1405        );
 1406        editor.move_right(&MoveRight, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(2, "αβ⋯".len())]
 1410        );
 1411        editor.move_right(&MoveRight, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416
 1417        editor.move_up(&MoveUp, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(1, "ab⋯e".len())]
 1421        );
 1422        editor.move_down(&MoveDown, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(2, "αβ⋯ε".len())]
 1426        );
 1427        editor.move_up(&MoveUp, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(1, "ab⋯e".len())]
 1431        );
 1432
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(0, "🟥🟧".len())]
 1437        );
 1438        editor.move_left(&MoveLeft, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(0, "🟥".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(0, "".len())]
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let editor = cx.add_window(|window, cx| {
 1456        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1457        build_editor(buffer, window, cx)
 1458    });
 1459    _ = editor.update(cx, |editor, window, cx| {
 1460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1461            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1462        });
 1463
 1464        // moving above start of document should move selection to start of document,
 1465        // but the next move down should still be at the original goal_x
 1466        editor.move_up(&MoveUp, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[empty_range(0, "".len())]
 1470        );
 1471
 1472        editor.move_down(&MoveDown, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(1, "abcd".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(2, "αβγ".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(3, "abcd".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1494        );
 1495
 1496        // moving past end of document should not change goal_x
 1497        editor.move_down(&MoveDown, window, cx);
 1498        assert_eq!(
 1499            editor.selections.display_ranges(cx),
 1500            &[empty_range(5, "".len())]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_up(&MoveUp, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(3, "abcd".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(2, "αβγ".len())]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532    let move_to_beg = MoveToBeginningOfLine {
 1533        stop_at_soft_wraps: true,
 1534        stop_at_indent: true,
 1535    };
 1536
 1537    let delete_to_beg = DeleteToBeginningOfLine {
 1538        stop_at_indent: false,
 1539    };
 1540
 1541    let move_to_end = MoveToEndOfLine {
 1542        stop_at_soft_wraps: true,
 1543    };
 1544
 1545    let editor = cx.add_window(|window, cx| {
 1546        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1547        build_editor(buffer, window, cx)
 1548    });
 1549    _ = editor.update(cx, |editor, window, cx| {
 1550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1551            s.select_display_ranges([
 1552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1553                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1554            ]);
 1555        });
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1586                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1587            ]
 1588        );
 1589    });
 1590
 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    // Moving to the end of line again is a no-op.
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_to_end_of_line(&move_to_end, window, cx);
 1605        assert_eq!(
 1606            editor.selections.display_ranges(cx),
 1607            &[
 1608                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1609                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1610            ]
 1611        );
 1612    });
 1613
 1614    _ = editor.update(cx, |editor, window, cx| {
 1615        editor.move_left(&MoveLeft, window, cx);
 1616        editor.select_to_beginning_of_line(
 1617            &SelectToBeginningOfLine {
 1618                stop_at_soft_wraps: true,
 1619                stop_at_indent: true,
 1620            },
 1621            window,
 1622            cx,
 1623        );
 1624        assert_eq!(
 1625            editor.selections.display_ranges(cx),
 1626            &[
 1627                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1628                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1629            ]
 1630        );
 1631    });
 1632
 1633    _ = editor.update(cx, |editor, window, cx| {
 1634        editor.select_to_beginning_of_line(
 1635            &SelectToBeginningOfLine {
 1636                stop_at_soft_wraps: true,
 1637                stop_at_indent: true,
 1638            },
 1639            window,
 1640            cx,
 1641        );
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[
 1645                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1646                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1647            ]
 1648        );
 1649    });
 1650
 1651    _ = editor.update(cx, |editor, window, cx| {
 1652        editor.select_to_beginning_of_line(
 1653            &SelectToBeginningOfLine {
 1654                stop_at_soft_wraps: true,
 1655                stop_at_indent: true,
 1656            },
 1657            window,
 1658            cx,
 1659        );
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[
 1663                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1664                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1665            ]
 1666        );
 1667    });
 1668
 1669    _ = editor.update(cx, |editor, window, cx| {
 1670        editor.select_to_end_of_line(
 1671            &SelectToEndOfLine {
 1672                stop_at_soft_wraps: true,
 1673            },
 1674            window,
 1675            cx,
 1676        );
 1677        assert_eq!(
 1678            editor.selections.display_ranges(cx),
 1679            &[
 1680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1681                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1682            ]
 1683        );
 1684    });
 1685
 1686    _ = editor.update(cx, |editor, window, cx| {
 1687        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1688        assert_eq!(editor.display_text(cx), "ab\n  de");
 1689        assert_eq!(
 1690            editor.selections.display_ranges(cx),
 1691            &[
 1692                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1693                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1694            ]
 1695        );
 1696    });
 1697
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1700        assert_eq!(editor.display_text(cx), "\n");
 1701        assert_eq!(
 1702            editor.selections.display_ranges(cx),
 1703            &[
 1704                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1706            ]
 1707        );
 1708    });
 1709}
 1710
 1711#[gpui::test]
 1712fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1713    init_test(cx, |_| {});
 1714    let move_to_beg = MoveToBeginningOfLine {
 1715        stop_at_soft_wraps: false,
 1716        stop_at_indent: false,
 1717    };
 1718
 1719    let move_to_end = MoveToEndOfLine {
 1720        stop_at_soft_wraps: false,
 1721    };
 1722
 1723    let editor = cx.add_window(|window, cx| {
 1724        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1725        build_editor(buffer, window, cx)
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.set_wrap_width(Some(140.0.into()), cx);
 1730
 1731        // We expect the following lines after wrapping
 1732        // ```
 1733        // thequickbrownfox
 1734        // jumpedoverthelazydo
 1735        // gs
 1736        // ```
 1737        // The final `gs` was soft-wrapped onto a new line.
 1738        assert_eq!(
 1739            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1740            editor.display_text(cx),
 1741        );
 1742
 1743        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1744        // Start the cursor at the `k` on the first line
 1745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1746            s.select_display_ranges([
 1747                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1748            ]);
 1749        });
 1750
 1751        // Moving to the beginning of the line should put us at the beginning of the line.
 1752        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1753        assert_eq!(
 1754            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1755            editor.selections.display_ranges(cx)
 1756        );
 1757
 1758        // Moving to the end of the line should put us at the end of the line.
 1759        editor.move_to_end_of_line(&move_to_end, window, cx);
 1760        assert_eq!(
 1761            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1762            editor.selections.display_ranges(cx)
 1763        );
 1764
 1765        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1766        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1768            s.select_display_ranges([
 1769                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1770            ]);
 1771        });
 1772
 1773        // Moving to the beginning of the line should put us at the start of the second line of
 1774        // display text, i.e., the `j`.
 1775        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1776        assert_eq!(
 1777            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1778            editor.selections.display_ranges(cx)
 1779        );
 1780
 1781        // Moving to the beginning of the line again should be a no-op.
 1782        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1783        assert_eq!(
 1784            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1785            editor.selections.display_ranges(cx)
 1786        );
 1787
 1788        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1789        // next display line.
 1790        editor.move_to_end_of_line(&move_to_end, window, cx);
 1791        assert_eq!(
 1792            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1793            editor.selections.display_ranges(cx)
 1794        );
 1795
 1796        // Moving to the end of the line again should be a no-op.
 1797        editor.move_to_end_of_line(&move_to_end, window, cx);
 1798        assert_eq!(
 1799            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1800            editor.selections.display_ranges(cx)
 1801        );
 1802    });
 1803}
 1804
 1805#[gpui::test]
 1806fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1807    init_test(cx, |_| {});
 1808
 1809    let move_to_beg = MoveToBeginningOfLine {
 1810        stop_at_soft_wraps: true,
 1811        stop_at_indent: true,
 1812    };
 1813
 1814    let select_to_beg = SelectToBeginningOfLine {
 1815        stop_at_soft_wraps: true,
 1816        stop_at_indent: true,
 1817    };
 1818
 1819    let delete_to_beg = DeleteToBeginningOfLine {
 1820        stop_at_indent: true,
 1821    };
 1822
 1823    let move_to_end = MoveToEndOfLine {
 1824        stop_at_soft_wraps: false,
 1825    };
 1826
 1827    let editor = cx.add_window(|window, cx| {
 1828        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1829        build_editor(buffer, window, cx)
 1830    });
 1831
 1832    _ = editor.update(cx, |editor, window, cx| {
 1833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1834            s.select_display_ranges([
 1835                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]);
 1838        });
 1839
 1840        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1841        // and the second cursor at the first non-whitespace character in the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1852        // and should move the second cursor to the beginning of the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1859            ]
 1860        );
 1861
 1862        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1863        // and should move the second cursor back to the first non-whitespace character in the line.
 1864        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1874        // and to the first non-whitespace character in the line for the second cursor.
 1875        editor.move_to_end_of_line(&move_to_end, window, cx);
 1876        editor.move_left(&MoveLeft, window, cx);
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1883            ]
 1884        );
 1885
 1886        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1887        // and should select to the beginning of the line for the second cursor.
 1888        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1889        assert_eq!(
 1890            editor.selections.display_ranges(cx),
 1891            &[
 1892                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1893                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1894            ]
 1895        );
 1896
 1897        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1898        // and should delete to the first non-whitespace character in the line for the second cursor.
 1899        editor.move_to_end_of_line(&move_to_end, window, cx);
 1900        editor.move_left(&MoveLeft, window, cx);
 1901        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1902        assert_eq!(editor.text(cx), "c\n  f");
 1903    });
 1904}
 1905
 1906#[gpui::test]
 1907fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1908    init_test(cx, |_| {});
 1909
 1910    let move_to_beg = MoveToBeginningOfLine {
 1911        stop_at_soft_wraps: true,
 1912        stop_at_indent: true,
 1913    };
 1914
 1915    let editor = cx.add_window(|window, cx| {
 1916        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1917        build_editor(buffer, window, cx)
 1918    });
 1919
 1920    _ = editor.update(cx, |editor, window, cx| {
 1921        // test cursor between line_start and indent_start
 1922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1923            s.select_display_ranges([
 1924                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1925            ]);
 1926        });
 1927
 1928        // cursor should move to line_start
 1929        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1930        assert_eq!(
 1931            editor.selections.display_ranges(cx),
 1932            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1933        );
 1934
 1935        // cursor should move to indent_start
 1936        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1937        assert_eq!(
 1938            editor.selections.display_ranges(cx),
 1939            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1940        );
 1941
 1942        // cursor should move to back to line_start
 1943        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1944        assert_eq!(
 1945            editor.selections.display_ranges(cx),
 1946            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1947        );
 1948    });
 1949}
 1950
 1951#[gpui::test]
 1952fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1953    init_test(cx, |_| {});
 1954
 1955    let editor = cx.add_window(|window, cx| {
 1956        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1957        build_editor(buffer, window, cx)
 1958    });
 1959    _ = editor.update(cx, |editor, window, cx| {
 1960        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1961            s.select_display_ranges([
 1962                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1963                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1964            ])
 1965        });
 1966        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1967        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1968
 1969        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1970        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1971
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_right(&MoveRight, window, cx);
 1991        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1992        assert_selection_ranges(
 1993            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1994            editor,
 1995            cx,
 1996        );
 1997
 1998        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1999        assert_selection_ranges(
 2000            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2001            editor,
 2002            cx,
 2003        );
 2004
 2005        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2006        assert_selection_ranges(
 2007            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2008            editor,
 2009            cx,
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022
 2023    _ = editor.update(cx, |editor, window, cx| {
 2024        editor.set_wrap_width(Some(140.0.into()), cx);
 2025        assert_eq!(
 2026            editor.display_text(cx),
 2027            "use one::{\n    two::three::\n    four::five\n};"
 2028        );
 2029
 2030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2031            s.select_display_ranges([
 2032                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2033            ]);
 2034        });
 2035
 2036        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2037        assert_eq!(
 2038            editor.selections.display_ranges(cx),
 2039            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2040        );
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2058        );
 2059
 2060        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2070        );
 2071    });
 2072}
 2073
 2074#[gpui::test]
 2075async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2076    init_test(cx, |_| {});
 2077    let mut cx = EditorTestContext::new(cx).await;
 2078
 2079    let line_height = cx.editor(|editor, window, _| {
 2080        editor
 2081            .style()
 2082            .unwrap()
 2083            .text
 2084            .line_height_in_pixels(window.rem_size())
 2085    });
 2086    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2087
 2088    cx.set_state(
 2089        &r#"ˇone
 2090        two
 2091
 2092        three
 2093        fourˇ
 2094        five
 2095
 2096        six"#
 2097            .unindent(),
 2098    );
 2099
 2100    cx.update_editor(|editor, window, cx| {
 2101        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2102    });
 2103    cx.assert_editor_state(
 2104        &r#"one
 2105        two
 2106        ˇ
 2107        three
 2108        four
 2109        five
 2110        ˇ
 2111        six"#
 2112            .unindent(),
 2113    );
 2114
 2115    cx.update_editor(|editor, window, cx| {
 2116        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2117    });
 2118    cx.assert_editor_state(
 2119        &r#"one
 2120        two
 2121
 2122        three
 2123        four
 2124        five
 2125        ˇ
 2126        sixˇ"#
 2127            .unindent(),
 2128    );
 2129
 2130    cx.update_editor(|editor, window, cx| {
 2131        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2132    });
 2133    cx.assert_editor_state(
 2134        &r#"one
 2135        two
 2136
 2137        three
 2138        four
 2139        five
 2140
 2141        sixˇ"#
 2142            .unindent(),
 2143    );
 2144
 2145    cx.update_editor(|editor, window, cx| {
 2146        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2147    });
 2148    cx.assert_editor_state(
 2149        &r#"one
 2150        two
 2151
 2152        three
 2153        four
 2154        five
 2155        ˇ
 2156        six"#
 2157            .unindent(),
 2158    );
 2159
 2160    cx.update_editor(|editor, window, cx| {
 2161        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2162    });
 2163    cx.assert_editor_state(
 2164        &r#"one
 2165        two
 2166        ˇ
 2167        three
 2168        four
 2169        five
 2170
 2171        six"#
 2172            .unindent(),
 2173    );
 2174
 2175    cx.update_editor(|editor, window, cx| {
 2176        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2177    });
 2178    cx.assert_editor_state(
 2179        &r#"ˇone
 2180        two
 2181
 2182        three
 2183        four
 2184        five
 2185
 2186        six"#
 2187            .unindent(),
 2188    );
 2189}
 2190
 2191#[gpui::test]
 2192async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2193    init_test(cx, |_| {});
 2194    let mut cx = EditorTestContext::new(cx).await;
 2195    let line_height = cx.editor(|editor, window, _| {
 2196        editor
 2197            .style()
 2198            .unwrap()
 2199            .text
 2200            .line_height_in_pixels(window.rem_size())
 2201    });
 2202    let window = cx.window;
 2203    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2204
 2205    cx.set_state(
 2206        r#"ˇone
 2207        two
 2208        three
 2209        four
 2210        five
 2211        six
 2212        seven
 2213        eight
 2214        nine
 2215        ten
 2216        "#,
 2217    );
 2218
 2219    cx.update_editor(|editor, window, cx| {
 2220        assert_eq!(
 2221            editor.snapshot(window, cx).scroll_position(),
 2222            gpui::Point::new(0., 0.)
 2223        );
 2224        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 3.)
 2228        );
 2229        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2230        assert_eq!(
 2231            editor.snapshot(window, cx).scroll_position(),
 2232            gpui::Point::new(0., 6.)
 2233        );
 2234        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 3.)
 2238        );
 2239
 2240        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 1.)
 2244        );
 2245        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.)
 2249        );
 2250    });
 2251}
 2252
 2253#[gpui::test]
 2254async fn test_autoscroll(cx: &mut TestAppContext) {
 2255    init_test(cx, |_| {});
 2256    let mut cx = EditorTestContext::new(cx).await;
 2257
 2258    let line_height = cx.update_editor(|editor, window, cx| {
 2259        editor.set_vertical_scroll_margin(2, cx);
 2260        editor
 2261            .style()
 2262            .unwrap()
 2263            .text
 2264            .line_height_in_pixels(window.rem_size())
 2265    });
 2266    let window = cx.window;
 2267    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2268
 2269    cx.set_state(
 2270        r#"ˇone
 2271            two
 2272            three
 2273            four
 2274            five
 2275            six
 2276            seven
 2277            eight
 2278            nine
 2279            ten
 2280        "#,
 2281    );
 2282    cx.update_editor(|editor, window, cx| {
 2283        assert_eq!(
 2284            editor.snapshot(window, cx).scroll_position(),
 2285            gpui::Point::new(0., 0.0)
 2286        );
 2287    });
 2288
 2289    // Add a cursor below the visible area. Since both cursors cannot fit
 2290    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2291    // allows the vertical scroll margin below that cursor.
 2292    cx.update_editor(|editor, window, cx| {
 2293        editor.change_selections(Default::default(), window, cx, |selections| {
 2294            selections.select_ranges([
 2295                Point::new(0, 0)..Point::new(0, 0),
 2296                Point::new(6, 0)..Point::new(6, 0),
 2297            ]);
 2298        })
 2299    });
 2300    cx.update_editor(|editor, window, cx| {
 2301        assert_eq!(
 2302            editor.snapshot(window, cx).scroll_position(),
 2303            gpui::Point::new(0., 3.0)
 2304        );
 2305    });
 2306
 2307    // Move down. The editor cursor scrolls down to track the newest cursor.
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_down(&Default::default(), window, cx);
 2310    });
 2311    cx.update_editor(|editor, window, cx| {
 2312        assert_eq!(
 2313            editor.snapshot(window, cx).scroll_position(),
 2314            gpui::Point::new(0., 4.0)
 2315        );
 2316    });
 2317
 2318    // Add a cursor above the visible area. Since both cursors fit on screen,
 2319    // the editor scrolls to show both.
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.change_selections(Default::default(), window, cx, |selections| {
 2322            selections.select_ranges([
 2323                Point::new(1, 0)..Point::new(1, 0),
 2324                Point::new(6, 0)..Point::new(6, 0),
 2325            ]);
 2326        })
 2327    });
 2328    cx.update_editor(|editor, window, cx| {
 2329        assert_eq!(
 2330            editor.snapshot(window, cx).scroll_position(),
 2331            gpui::Point::new(0., 1.0)
 2332        );
 2333    });
 2334}
 2335
 2336#[gpui::test]
 2337async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2338    init_test(cx, |_| {});
 2339    let mut cx = EditorTestContext::new(cx).await;
 2340
 2341    let line_height = cx.editor(|editor, window, _cx| {
 2342        editor
 2343            .style()
 2344            .unwrap()
 2345            .text
 2346            .line_height_in_pixels(window.rem_size())
 2347    });
 2348    let window = cx.window;
 2349    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2350    cx.set_state(
 2351        &r#"
 2352        ˇone
 2353        two
 2354        threeˇ
 2355        four
 2356        five
 2357        six
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| {
 2367        editor.move_page_down(&MovePageDown::default(), window, cx)
 2368    });
 2369    cx.assert_editor_state(
 2370        &r#"
 2371        one
 2372        two
 2373        three
 2374        ˇfour
 2375        five
 2376        sixˇ
 2377        seven
 2378        eight
 2379        nine
 2380        ten
 2381        "#
 2382        .unindent(),
 2383    );
 2384
 2385    cx.update_editor(|editor, 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    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2405    cx.assert_editor_state(
 2406        &r#"
 2407        one
 2408        two
 2409        three
 2410        ˇfour
 2411        five
 2412        sixˇ
 2413        seven
 2414        eight
 2415        nine
 2416        ten
 2417        "#
 2418        .unindent(),
 2419    );
 2420
 2421    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2422    cx.assert_editor_state(
 2423        &r#"
 2424        ˇone
 2425        two
 2426        threeˇ
 2427        four
 2428        five
 2429        six
 2430        seven
 2431        eight
 2432        nine
 2433        ten
 2434        "#
 2435        .unindent(),
 2436    );
 2437
 2438    // Test select collapsing
 2439    cx.update_editor(|editor, window, cx| {
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441        editor.move_page_down(&MovePageDown::default(), window, cx);
 2442        editor.move_page_down(&MovePageDown::default(), window, cx);
 2443    });
 2444    cx.assert_editor_state(
 2445        &r#"
 2446        one
 2447        two
 2448        three
 2449        four
 2450        five
 2451        six
 2452        seven
 2453        eight
 2454        nine
 2455        ˇten
 2456        ˇ"#
 2457        .unindent(),
 2458    );
 2459}
 2460
 2461#[gpui::test]
 2462async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2463    init_test(cx, |_| {});
 2464    let mut cx = EditorTestContext::new(cx).await;
 2465    cx.set_state("one «two threeˇ» four");
 2466    cx.update_editor(|editor, window, cx| {
 2467        editor.delete_to_beginning_of_line(
 2468            &DeleteToBeginningOfLine {
 2469                stop_at_indent: false,
 2470            },
 2471            window,
 2472            cx,
 2473        );
 2474        assert_eq!(editor.text(cx), " four");
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let editor = cx.add_window(|window, cx| {
 2483        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2484        build_editor(buffer, window, cx)
 2485    });
 2486
 2487    _ = editor.update(cx, |editor, window, cx| {
 2488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2489            s.select_display_ranges([
 2490                // an empty selection - the preceding word fragment is deleted
 2491                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2492                // characters selected - they are deleted
 2493                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2494            ])
 2495        });
 2496        editor.delete_to_previous_word_start(
 2497            &DeleteToPreviousWordStart {
 2498                ignore_newlines: false,
 2499            },
 2500            window,
 2501            cx,
 2502        );
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2504    });
 2505
 2506    _ = editor.update(cx, |editor, window, cx| {
 2507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2508            s.select_display_ranges([
 2509                // an empty selection - the following word fragment is deleted
 2510                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2511                // characters selected - they are deleted
 2512                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2513            ])
 2514        });
 2515        editor.delete_to_next_word_end(
 2516            &DeleteToNextWordEnd {
 2517                ignore_newlines: false,
 2518            },
 2519            window,
 2520            cx,
 2521        );
 2522        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2523    });
 2524}
 2525
 2526#[gpui::test]
 2527fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2528    init_test(cx, |_| {});
 2529
 2530    let editor = cx.add_window(|window, cx| {
 2531        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2532        build_editor(buffer, window, cx)
 2533    });
 2534    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2535        ignore_newlines: false,
 2536    };
 2537    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2538        ignore_newlines: true,
 2539    };
 2540
 2541    _ = editor.update(cx, |editor, window, cx| {
 2542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2543            s.select_display_ranges([
 2544                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2545            ])
 2546        });
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2557        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2558        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2559    });
 2560}
 2561
 2562#[gpui::test]
 2563fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2564    init_test(cx, |_| {});
 2565
 2566    let editor = cx.add_window(|window, cx| {
 2567        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2568        build_editor(buffer, window, cx)
 2569    });
 2570    let del_to_next_word_end = DeleteToNextWordEnd {
 2571        ignore_newlines: false,
 2572    };
 2573    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2574        ignore_newlines: true,
 2575    };
 2576
 2577    _ = editor.update(cx, |editor, window, cx| {
 2578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2579            s.select_display_ranges([
 2580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2581            ])
 2582        });
 2583        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2584        assert_eq!(
 2585            editor.buffer.read(cx).read(cx).text(),
 2586            "one\n   two\nthree\n   four"
 2587        );
 2588        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2589        assert_eq!(
 2590            editor.buffer.read(cx).read(cx).text(),
 2591            "\n   two\nthree\n   four"
 2592        );
 2593        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2594        assert_eq!(
 2595            editor.buffer.read(cx).read(cx).text(),
 2596            "two\nthree\n   four"
 2597        );
 2598        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2602        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2603        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2604    });
 2605}
 2606
 2607#[gpui::test]
 2608fn test_newline(cx: &mut TestAppContext) {
 2609    init_test(cx, |_| {});
 2610
 2611    let editor = cx.add_window(|window, cx| {
 2612        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2613        build_editor(buffer, window, cx)
 2614    });
 2615
 2616    _ = editor.update(cx, |editor, window, cx| {
 2617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2618            s.select_display_ranges([
 2619                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2621                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2622            ])
 2623        });
 2624
 2625        editor.newline(&Newline, window, cx);
 2626        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2627    });
 2628}
 2629
 2630#[gpui::test]
 2631fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2632    init_test(cx, |_| {});
 2633
 2634    let editor = cx.add_window(|window, cx| {
 2635        let buffer = MultiBuffer::build_simple(
 2636            "
 2637                a
 2638                b(
 2639                    X
 2640                )
 2641                c(
 2642                    X
 2643                )
 2644            "
 2645            .unindent()
 2646            .as_str(),
 2647            cx,
 2648        );
 2649        let mut editor = build_editor(buffer, window, cx);
 2650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2651            s.select_ranges([
 2652                Point::new(2, 4)..Point::new(2, 5),
 2653                Point::new(5, 4)..Point::new(5, 5),
 2654            ])
 2655        });
 2656        editor
 2657    });
 2658
 2659    _ = editor.update(cx, |editor, window, cx| {
 2660        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2661        editor.buffer.update(cx, |buffer, cx| {
 2662            buffer.edit(
 2663                [
 2664                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2665                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2666                ],
 2667                None,
 2668                cx,
 2669            );
 2670            assert_eq!(
 2671                buffer.read(cx).text(),
 2672                "
 2673                    a
 2674                    b()
 2675                    c()
 2676                "
 2677                .unindent()
 2678            );
 2679        });
 2680        assert_eq!(
 2681            editor.selections.ranges(cx),
 2682            &[
 2683                Point::new(1, 2)..Point::new(1, 2),
 2684                Point::new(2, 2)..Point::new(2, 2),
 2685            ],
 2686        );
 2687
 2688        editor.newline(&Newline, window, cx);
 2689        assert_eq!(
 2690            editor.text(cx),
 2691            "
 2692                a
 2693                b(
 2694                )
 2695                c(
 2696                )
 2697            "
 2698            .unindent()
 2699        );
 2700
 2701        // The selections are moved after the inserted newlines
 2702        assert_eq!(
 2703            editor.selections.ranges(cx),
 2704            &[
 2705                Point::new(2, 0)..Point::new(2, 0),
 2706                Point::new(4, 0)..Point::new(4, 0),
 2707            ],
 2708        );
 2709    });
 2710}
 2711
 2712#[gpui::test]
 2713async fn test_newline_above(cx: &mut TestAppContext) {
 2714    init_test(cx, |settings| {
 2715        settings.defaults.tab_size = NonZeroU32::new(4)
 2716    });
 2717
 2718    let language = Arc::new(
 2719        Language::new(
 2720            LanguageConfig::default(),
 2721            Some(tree_sitter_rust::LANGUAGE.into()),
 2722        )
 2723        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2724        .unwrap(),
 2725    );
 2726
 2727    let mut cx = EditorTestContext::new(cx).await;
 2728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2729    cx.set_state(indoc! {"
 2730        const a: ˇA = (
 2731 2732                «const_functionˇ»(ˇ),
 2733                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2734 2735        ˇ);ˇ
 2736    "});
 2737
 2738    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2739    cx.assert_editor_state(indoc! {"
 2740        ˇ
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                ˇ
 2746                const_function(),
 2747                ˇ
 2748                ˇ
 2749                ˇ
 2750                ˇ
 2751                something_else,
 2752                ˇ
 2753            )
 2754            ˇ
 2755            ˇ
 2756        );
 2757    "});
 2758}
 2759
 2760#[gpui::test]
 2761async fn test_newline_below(cx: &mut TestAppContext) {
 2762    init_test(cx, |settings| {
 2763        settings.defaults.tab_size = NonZeroU32::new(4)
 2764    });
 2765
 2766    let language = Arc::new(
 2767        Language::new(
 2768            LanguageConfig::default(),
 2769            Some(tree_sitter_rust::LANGUAGE.into()),
 2770        )
 2771        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2772        .unwrap(),
 2773    );
 2774
 2775    let mut cx = EditorTestContext::new(cx).await;
 2776    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777    cx.set_state(indoc! {"
 2778        const a: ˇA = (
 2779 2780                «const_functionˇ»(ˇ),
 2781                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2782 2783        ˇ);ˇ
 2784    "});
 2785
 2786    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2787    cx.assert_editor_state(indoc! {"
 2788        const a: A = (
 2789            ˇ
 2790            (
 2791                ˇ
 2792                const_function(),
 2793                ˇ
 2794                ˇ
 2795                something_else,
 2796                ˇ
 2797                ˇ
 2798                ˇ
 2799                ˇ
 2800            )
 2801            ˇ
 2802        );
 2803        ˇ
 2804        ˇ
 2805    "});
 2806}
 2807
 2808#[gpui::test]
 2809async fn test_newline_comments(cx: &mut TestAppContext) {
 2810    init_test(cx, |settings| {
 2811        settings.defaults.tab_size = NonZeroU32::new(4)
 2812    });
 2813
 2814    let language = Arc::new(Language::new(
 2815        LanguageConfig {
 2816            line_comments: vec!["// ".into()],
 2817            ..LanguageConfig::default()
 2818        },
 2819        None,
 2820    ));
 2821    {
 2822        let mut cx = EditorTestContext::new(cx).await;
 2823        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2824        cx.set_state(indoc! {"
 2825        // Fooˇ
 2826    "});
 2827
 2828        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2829        cx.assert_editor_state(indoc! {"
 2830        // Foo
 2831        // ˇ
 2832    "});
 2833        // Ensure that we add comment prefix when existing line contains space
 2834        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2835        cx.assert_editor_state(
 2836            indoc! {"
 2837        // Foo
 2838        //s
 2839        // ˇ
 2840    "}
 2841            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2842            .as_str(),
 2843        );
 2844        // Ensure that we add comment prefix when existing line does not contain space
 2845        cx.set_state(indoc! {"
 2846        // Foo
 2847        //ˇ
 2848    "});
 2849        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2850        cx.assert_editor_state(indoc! {"
 2851        // Foo
 2852        //
 2853        // ˇ
 2854    "});
 2855        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2856        cx.set_state(indoc! {"
 2857        ˇ// Foo
 2858    "});
 2859        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2860        cx.assert_editor_state(indoc! {"
 2861
 2862        ˇ// Foo
 2863    "});
 2864    }
 2865    // Ensure that comment continuations can be disabled.
 2866    update_test_language_settings(cx, |settings| {
 2867        settings.defaults.extend_comment_on_newline = Some(false);
 2868    });
 2869    let mut cx = EditorTestContext::new(cx).await;
 2870    cx.set_state(indoc! {"
 2871        // Fooˇ
 2872    "});
 2873    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2874    cx.assert_editor_state(indoc! {"
 2875        // Foo
 2876        ˇ
 2877    "});
 2878}
 2879
 2880#[gpui::test]
 2881async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2882    init_test(cx, |settings| {
 2883        settings.defaults.tab_size = NonZeroU32::new(4)
 2884    });
 2885
 2886    let language = Arc::new(Language::new(
 2887        LanguageConfig {
 2888            line_comments: vec!["// ".into(), "/// ".into()],
 2889            ..LanguageConfig::default()
 2890        },
 2891        None,
 2892    ));
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        //ˇ
 2898    "});
 2899        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2900        cx.assert_editor_state(indoc! {"
 2901        //
 2902        // ˇ
 2903    "});
 2904
 2905        cx.set_state(indoc! {"
 2906        ///ˇ
 2907    "});
 2908        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2909        cx.assert_editor_state(indoc! {"
 2910        ///
 2911        /// ˇ
 2912    "});
 2913    }
 2914}
 2915
 2916#[gpui::test]
 2917async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2918    init_test(cx, |settings| {
 2919        settings.defaults.tab_size = NonZeroU32::new(4)
 2920    });
 2921
 2922    let language = Arc::new(
 2923        Language::new(
 2924            LanguageConfig {
 2925                documentation_comment: Some(language::BlockCommentConfig {
 2926                    start: "/**".into(),
 2927                    end: "*/".into(),
 2928                    prefix: "* ".into(),
 2929                    tab_size: 1,
 2930                }),
 2931
 2932                ..LanguageConfig::default()
 2933            },
 2934            Some(tree_sitter_rust::LANGUAGE.into()),
 2935        )
 2936        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2937        .unwrap(),
 2938    );
 2939
 2940    {
 2941        let mut cx = EditorTestContext::new(cx).await;
 2942        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2943        cx.set_state(indoc! {"
 2944        /**ˇ
 2945    "});
 2946
 2947        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2948        cx.assert_editor_state(indoc! {"
 2949        /**
 2950         * ˇ
 2951    "});
 2952        // Ensure that if cursor is before the comment start,
 2953        // we do not actually insert a comment prefix.
 2954        cx.set_state(indoc! {"
 2955        ˇ/**
 2956    "});
 2957        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2958        cx.assert_editor_state(indoc! {"
 2959
 2960        ˇ/**
 2961    "});
 2962        // Ensure that if cursor is between it doesn't add comment prefix.
 2963        cx.set_state(indoc! {"
 2964        /*ˇ*
 2965    "});
 2966        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2967        cx.assert_editor_state(indoc! {"
 2968        /*
 2969        ˇ*
 2970    "});
 2971        // Ensure that if suffix exists on same line after cursor it adds new line.
 2972        cx.set_state(indoc! {"
 2973        /**ˇ*/
 2974    "});
 2975        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2976        cx.assert_editor_state(indoc! {"
 2977        /**
 2978         * ˇ
 2979         */
 2980    "});
 2981        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2982        cx.set_state(indoc! {"
 2983        /**ˇ */
 2984    "});
 2985        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2986        cx.assert_editor_state(indoc! {"
 2987        /**
 2988         * ˇ
 2989         */
 2990    "});
 2991        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2992        cx.set_state(indoc! {"
 2993        /** ˇ*/
 2994    "});
 2995        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2996        cx.assert_editor_state(
 2997            indoc! {"
 2998        /**s
 2999         * ˇ
 3000         */
 3001    "}
 3002            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3003            .as_str(),
 3004        );
 3005        // Ensure that delimiter space is preserved when newline on already
 3006        // spaced delimiter.
 3007        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3008        cx.assert_editor_state(
 3009            indoc! {"
 3010        /**s
 3011         *s
 3012         * ˇ
 3013         */
 3014    "}
 3015            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3016            .as_str(),
 3017        );
 3018        // Ensure that delimiter space is preserved when space is not
 3019        // on existing delimiter.
 3020        cx.set_state(indoc! {"
 3021        /**
 3022 3023         */
 3024    "});
 3025        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3026        cx.assert_editor_state(indoc! {"
 3027        /**
 3028         *
 3029         * ˇ
 3030         */
 3031    "});
 3032        // Ensure that if suffix exists on same line after cursor it
 3033        // doesn't add extra new line if prefix is not on same line.
 3034        cx.set_state(indoc! {"
 3035        /**
 3036        ˇ*/
 3037    "});
 3038        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3039        cx.assert_editor_state(indoc! {"
 3040        /**
 3041
 3042        ˇ*/
 3043    "});
 3044        // Ensure that it detects suffix after existing prefix.
 3045        cx.set_state(indoc! {"
 3046        /**ˇ/
 3047    "});
 3048        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3049        cx.assert_editor_state(indoc! {"
 3050        /**
 3051        ˇ/
 3052    "});
 3053        // Ensure that if suffix exists on same line before
 3054        // cursor it does not add comment prefix.
 3055        cx.set_state(indoc! {"
 3056        /** */ˇ
 3057    "});
 3058        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3059        cx.assert_editor_state(indoc! {"
 3060        /** */
 3061        ˇ
 3062    "});
 3063        // Ensure that if suffix exists on same line before
 3064        // cursor it does not add comment prefix.
 3065        cx.set_state(indoc! {"
 3066        /**
 3067         *
 3068         */ˇ
 3069    "});
 3070        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3071        cx.assert_editor_state(indoc! {"
 3072        /**
 3073         *
 3074         */
 3075         ˇ
 3076    "});
 3077
 3078        // Ensure that inline comment followed by code
 3079        // doesn't add comment prefix on newline
 3080        cx.set_state(indoc! {"
 3081        /** */ textˇ
 3082    "});
 3083        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3084        cx.assert_editor_state(indoc! {"
 3085        /** */ text
 3086        ˇ
 3087    "});
 3088
 3089        // Ensure that text after comment end tag
 3090        // doesn't add comment prefix on newline
 3091        cx.set_state(indoc! {"
 3092        /**
 3093         *
 3094         */ˇtext
 3095    "});
 3096        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3097        cx.assert_editor_state(indoc! {"
 3098        /**
 3099         *
 3100         */
 3101         ˇtext
 3102    "});
 3103
 3104        // Ensure if not comment block it doesn't
 3105        // add comment prefix on newline
 3106        cx.set_state(indoc! {"
 3107        * textˇ
 3108    "});
 3109        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3110        cx.assert_editor_state(indoc! {"
 3111        * text
 3112        ˇ
 3113    "});
 3114    }
 3115    // Ensure that comment continuations can be disabled.
 3116    update_test_language_settings(cx, |settings| {
 3117        settings.defaults.extend_comment_on_newline = Some(false);
 3118    });
 3119    let mut cx = EditorTestContext::new(cx).await;
 3120    cx.set_state(indoc! {"
 3121        /**ˇ
 3122    "});
 3123    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3124    cx.assert_editor_state(indoc! {"
 3125        /**
 3126        ˇ
 3127    "});
 3128}
 3129
 3130#[gpui::test]
 3131async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3132    init_test(cx, |settings| {
 3133        settings.defaults.tab_size = NonZeroU32::new(4)
 3134    });
 3135
 3136    let lua_language = Arc::new(Language::new(
 3137        LanguageConfig {
 3138            line_comments: vec!["--".into()],
 3139            block_comment: Some(language::BlockCommentConfig {
 3140                start: "--[[".into(),
 3141                prefix: "".into(),
 3142                end: "]]".into(),
 3143                tab_size: 0,
 3144            }),
 3145            ..LanguageConfig::default()
 3146        },
 3147        None,
 3148    ));
 3149
 3150    let mut cx = EditorTestContext::new(cx).await;
 3151    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3152
 3153    // Line with line comment should extend
 3154    cx.set_state(indoc! {"
 3155        --ˇ
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        --
 3160        --ˇ
 3161    "});
 3162
 3163    // Line with block comment that matches line comment should not extend
 3164    cx.set_state(indoc! {"
 3165        --[[ˇ
 3166    "});
 3167    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168    cx.assert_editor_state(indoc! {"
 3169        --[[
 3170        ˇ
 3171    "});
 3172}
 3173
 3174#[gpui::test]
 3175fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3176    init_test(cx, |_| {});
 3177
 3178    let editor = cx.add_window(|window, cx| {
 3179        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3180        let mut editor = build_editor(buffer, window, cx);
 3181        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3182            s.select_ranges([3..4, 11..12, 19..20])
 3183        });
 3184        editor
 3185    });
 3186
 3187    _ = editor.update(cx, |editor, window, cx| {
 3188        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3189        editor.buffer.update(cx, |buffer, cx| {
 3190            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3191            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3192        });
 3193        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3194
 3195        editor.insert("Z", window, cx);
 3196        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3197
 3198        // The selections are moved after the inserted characters
 3199        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3200    });
 3201}
 3202
 3203#[gpui::test]
 3204async fn test_tab(cx: &mut TestAppContext) {
 3205    init_test(cx, |settings| {
 3206        settings.defaults.tab_size = NonZeroU32::new(3)
 3207    });
 3208
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        ˇabˇc
 3212        ˇ🏀ˇ🏀ˇefg
 3213 3214    "});
 3215    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217           ˇab ˇc
 3218           ˇ🏀  ˇ🏀  ˇefg
 3219        d  ˇ
 3220    "});
 3221
 3222    cx.set_state(indoc! {"
 3223        a
 3224        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3225    "});
 3226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3227    cx.assert_editor_state(indoc! {"
 3228        a
 3229           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3230    "});
 3231}
 3232
 3233#[gpui::test]
 3234async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3235    init_test(cx, |_| {});
 3236
 3237    let mut cx = EditorTestContext::new(cx).await;
 3238    let language = Arc::new(
 3239        Language::new(
 3240            LanguageConfig::default(),
 3241            Some(tree_sitter_rust::LANGUAGE.into()),
 3242        )
 3243        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3244        .unwrap(),
 3245    );
 3246    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3247
 3248    // test when all cursors are not at suggested indent
 3249    // then simply move to their suggested indent location
 3250    cx.set_state(indoc! {"
 3251        const a: B = (
 3252            c(
 3253        ˇ
 3254        ˇ    )
 3255        );
 3256    "});
 3257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3258    cx.assert_editor_state(indoc! {"
 3259        const a: B = (
 3260            c(
 3261                ˇ
 3262            ˇ)
 3263        );
 3264    "});
 3265
 3266    // test cursor already at suggested indent not moving when
 3267    // other cursors are yet to reach their suggested indents
 3268    cx.set_state(indoc! {"
 3269        ˇ
 3270        const a: B = (
 3271            c(
 3272                d(
 3273        ˇ
 3274                )
 3275        ˇ
 3276        ˇ    )
 3277        );
 3278    "});
 3279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3280    cx.assert_editor_state(indoc! {"
 3281        ˇ
 3282        const a: B = (
 3283            c(
 3284                d(
 3285                    ˇ
 3286                )
 3287                ˇ
 3288            ˇ)
 3289        );
 3290    "});
 3291    // test when all cursors are at suggested indent then tab is inserted
 3292    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3293    cx.assert_editor_state(indoc! {"
 3294            ˇ
 3295        const a: B = (
 3296            c(
 3297                d(
 3298                        ˇ
 3299                )
 3300                    ˇ
 3301                ˇ)
 3302        );
 3303    "});
 3304
 3305    // test when current indent is less than suggested indent,
 3306    // we adjust line to match suggested indent and move cursor to it
 3307    //
 3308    // when no other cursor is at word boundary, all of them should move
 3309    cx.set_state(indoc! {"
 3310        const a: B = (
 3311            c(
 3312                d(
 3313        ˇ
 3314        ˇ   )
 3315        ˇ   )
 3316        );
 3317    "});
 3318    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        const a: B = (
 3321            c(
 3322                d(
 3323                    ˇ
 3324                ˇ)
 3325            ˇ)
 3326        );
 3327    "});
 3328
 3329    // test when current indent is less than suggested indent,
 3330    // we adjust line to match suggested indent and move cursor to it
 3331    //
 3332    // when some other cursor is at word boundary, it should not move
 3333    cx.set_state(indoc! {"
 3334        const a: B = (
 3335            c(
 3336                d(
 3337        ˇ
 3338        ˇ   )
 3339           ˇ)
 3340        );
 3341    "});
 3342    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3343    cx.assert_editor_state(indoc! {"
 3344        const a: B = (
 3345            c(
 3346                d(
 3347                    ˇ
 3348                ˇ)
 3349            ˇ)
 3350        );
 3351    "});
 3352
 3353    // test when current indent is more than suggested indent,
 3354    // we just move cursor to current indent instead of suggested indent
 3355    //
 3356    // when no other cursor is at word boundary, all of them should move
 3357    cx.set_state(indoc! {"
 3358        const a: B = (
 3359            c(
 3360                d(
 3361        ˇ
 3362        ˇ                )
 3363        ˇ   )
 3364        );
 3365    "});
 3366    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: B = (
 3369            c(
 3370                d(
 3371                    ˇ
 3372                        ˇ)
 3373            ˇ)
 3374        );
 3375    "});
 3376    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3377    cx.assert_editor_state(indoc! {"
 3378        const a: B = (
 3379            c(
 3380                d(
 3381                        ˇ
 3382                            ˇ)
 3383                ˇ)
 3384        );
 3385    "});
 3386
 3387    // test when current indent is more than suggested indent,
 3388    // we just move cursor to current indent instead of suggested indent
 3389    //
 3390    // when some other cursor is at word boundary, it doesn't move
 3391    cx.set_state(indoc! {"
 3392        const a: B = (
 3393            c(
 3394                d(
 3395        ˇ
 3396        ˇ                )
 3397            ˇ)
 3398        );
 3399    "});
 3400    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3401    cx.assert_editor_state(indoc! {"
 3402        const a: B = (
 3403            c(
 3404                d(
 3405                    ˇ
 3406                        ˇ)
 3407            ˇ)
 3408        );
 3409    "});
 3410
 3411    // handle auto-indent when there are multiple cursors on the same line
 3412    cx.set_state(indoc! {"
 3413        const a: B = (
 3414            c(
 3415        ˇ    ˇ
 3416        ˇ    )
 3417        );
 3418    "});
 3419    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3420    cx.assert_editor_state(indoc! {"
 3421        const a: B = (
 3422            c(
 3423                ˇ
 3424            ˇ)
 3425        );
 3426    "});
 3427}
 3428
 3429#[gpui::test]
 3430async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3431    init_test(cx, |settings| {
 3432        settings.defaults.tab_size = NonZeroU32::new(3)
 3433    });
 3434
 3435    let mut cx = EditorTestContext::new(cx).await;
 3436    cx.set_state(indoc! {"
 3437         ˇ
 3438        \t ˇ
 3439        \t  ˇ
 3440        \t   ˇ
 3441         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3442    "});
 3443
 3444    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3445    cx.assert_editor_state(indoc! {"
 3446           ˇ
 3447        \t   ˇ
 3448        \t   ˇ
 3449        \t      ˇ
 3450         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3451    "});
 3452}
 3453
 3454#[gpui::test]
 3455async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3456    init_test(cx, |settings| {
 3457        settings.defaults.tab_size = NonZeroU32::new(4)
 3458    });
 3459
 3460    let language = Arc::new(
 3461        Language::new(
 3462            LanguageConfig::default(),
 3463            Some(tree_sitter_rust::LANGUAGE.into()),
 3464        )
 3465        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3466        .unwrap(),
 3467    );
 3468
 3469    let mut cx = EditorTestContext::new(cx).await;
 3470    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3471    cx.set_state(indoc! {"
 3472        fn a() {
 3473            if b {
 3474        \t ˇc
 3475            }
 3476        }
 3477    "});
 3478
 3479    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3480    cx.assert_editor_state(indoc! {"
 3481        fn a() {
 3482            if b {
 3483                ˇc
 3484            }
 3485        }
 3486    "});
 3487}
 3488
 3489#[gpui::test]
 3490async fn test_indent_outdent(cx: &mut TestAppContext) {
 3491    init_test(cx, |settings| {
 3492        settings.defaults.tab_size = NonZeroU32::new(4);
 3493    });
 3494
 3495    let mut cx = EditorTestContext::new(cx).await;
 3496
 3497    cx.set_state(indoc! {"
 3498          «oneˇ» «twoˇ»
 3499        three
 3500         four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504            «oneˇ» «twoˇ»
 3505        three
 3506         four
 3507    "});
 3508
 3509    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3510    cx.assert_editor_state(indoc! {"
 3511        «oneˇ» «twoˇ»
 3512        three
 3513         four
 3514    "});
 3515
 3516    // select across line ending
 3517    cx.set_state(indoc! {"
 3518        one two
 3519        t«hree
 3520        ˇ» four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525            t«hree
 3526        ˇ» four
 3527    "});
 3528
 3529    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3530    cx.assert_editor_state(indoc! {"
 3531        one two
 3532        t«hree
 3533        ˇ» four
 3534    "});
 3535
 3536    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3537    cx.set_state(indoc! {"
 3538        one two
 3539        ˇthree
 3540            four
 3541    "});
 3542    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3543    cx.assert_editor_state(indoc! {"
 3544        one two
 3545            ˇthree
 3546            four
 3547    "});
 3548
 3549    cx.set_state(indoc! {"
 3550        one two
 3551        ˇ    three
 3552            four
 3553    "});
 3554    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556        one two
 3557        ˇthree
 3558            four
 3559    "});
 3560}
 3561
 3562#[gpui::test]
 3563async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3564    // This is a regression test for issue #33761
 3565    init_test(cx, |_| {});
 3566
 3567    let mut cx = EditorTestContext::new(cx).await;
 3568    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3569    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3570
 3571    cx.set_state(
 3572        r#"ˇ#     ingress:
 3573ˇ#         api:
 3574ˇ#             enabled: false
 3575ˇ#             pathType: Prefix
 3576ˇ#           console:
 3577ˇ#               enabled: false
 3578ˇ#               pathType: Prefix
 3579"#,
 3580    );
 3581
 3582    // Press tab to indent all lines
 3583    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3584
 3585    cx.assert_editor_state(
 3586        r#"    ˇ#     ingress:
 3587    ˇ#         api:
 3588    ˇ#             enabled: false
 3589    ˇ#             pathType: Prefix
 3590    ˇ#           console:
 3591    ˇ#               enabled: false
 3592    ˇ#               pathType: Prefix
 3593"#,
 3594    );
 3595}
 3596
 3597#[gpui::test]
 3598async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3599    // This is a test to make sure our fix for issue #33761 didn't break anything
 3600    init_test(cx, |_| {});
 3601
 3602    let mut cx = EditorTestContext::new(cx).await;
 3603    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3605
 3606    cx.set_state(
 3607        r#"ˇingress:
 3608ˇ  api:
 3609ˇ    enabled: false
 3610ˇ    pathType: Prefix
 3611"#,
 3612    );
 3613
 3614    // Press tab to indent all lines
 3615    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3616
 3617    cx.assert_editor_state(
 3618        r#"ˇingress:
 3619    ˇapi:
 3620        ˇenabled: false
 3621        ˇpathType: Prefix
 3622"#,
 3623    );
 3624}
 3625
 3626#[gpui::test]
 3627async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3628    init_test(cx, |settings| {
 3629        settings.defaults.hard_tabs = Some(true);
 3630    });
 3631
 3632    let mut cx = EditorTestContext::new(cx).await;
 3633
 3634    // select two ranges on one line
 3635    cx.set_state(indoc! {"
 3636        «oneˇ» «twoˇ»
 3637        three
 3638        four
 3639    "});
 3640    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3641    cx.assert_editor_state(indoc! {"
 3642        \t«oneˇ» «twoˇ»
 3643        three
 3644        four
 3645    "});
 3646    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3647    cx.assert_editor_state(indoc! {"
 3648        \t\t«oneˇ» «twoˇ»
 3649        three
 3650        four
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        \t«oneˇ» «twoˇ»
 3655        three
 3656        four
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        «oneˇ» «twoˇ»
 3661        three
 3662        four
 3663    "});
 3664
 3665    // select across a line ending
 3666    cx.set_state(indoc! {"
 3667        one two
 3668        t«hree
 3669        ˇ»four
 3670    "});
 3671    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3672    cx.assert_editor_state(indoc! {"
 3673        one two
 3674        \tt«hree
 3675        ˇ»four
 3676    "});
 3677    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3678    cx.assert_editor_state(indoc! {"
 3679        one two
 3680        \t\tt«hree
 3681        ˇ»four
 3682    "});
 3683    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3684    cx.assert_editor_state(indoc! {"
 3685        one two
 3686        \tt«hree
 3687        ˇ»four
 3688    "});
 3689    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3690    cx.assert_editor_state(indoc! {"
 3691        one two
 3692        t«hree
 3693        ˇ»four
 3694    "});
 3695
 3696    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3697    cx.set_state(indoc! {"
 3698        one two
 3699        ˇthree
 3700        four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        one two
 3705        ˇthree
 3706        four
 3707    "});
 3708    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3709    cx.assert_editor_state(indoc! {"
 3710        one two
 3711        \tˇthree
 3712        four
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        one two
 3717        ˇthree
 3718        four
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3724    init_test(cx, |settings| {
 3725        settings.languages.0.extend([
 3726            (
 3727                "TOML".into(),
 3728                LanguageSettingsContent {
 3729                    tab_size: NonZeroU32::new(2),
 3730                    ..Default::default()
 3731                },
 3732            ),
 3733            (
 3734                "Rust".into(),
 3735                LanguageSettingsContent {
 3736                    tab_size: NonZeroU32::new(4),
 3737                    ..Default::default()
 3738                },
 3739            ),
 3740        ]);
 3741    });
 3742
 3743    let toml_language = Arc::new(Language::new(
 3744        LanguageConfig {
 3745            name: "TOML".into(),
 3746            ..Default::default()
 3747        },
 3748        None,
 3749    ));
 3750    let rust_language = Arc::new(Language::new(
 3751        LanguageConfig {
 3752            name: "Rust".into(),
 3753            ..Default::default()
 3754        },
 3755        None,
 3756    ));
 3757
 3758    let toml_buffer =
 3759        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3760    let rust_buffer =
 3761        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3762    let multibuffer = cx.new(|cx| {
 3763        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3764        multibuffer.push_excerpts(
 3765            toml_buffer.clone(),
 3766            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3767            cx,
 3768        );
 3769        multibuffer.push_excerpts(
 3770            rust_buffer.clone(),
 3771            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3772            cx,
 3773        );
 3774        multibuffer
 3775    });
 3776
 3777    cx.add_window(|window, cx| {
 3778        let mut editor = build_editor(multibuffer, window, cx);
 3779
 3780        assert_eq!(
 3781            editor.text(cx),
 3782            indoc! {"
 3783                a = 1
 3784                b = 2
 3785
 3786                const c: usize = 3;
 3787            "}
 3788        );
 3789
 3790        select_ranges(
 3791            &mut editor,
 3792            indoc! {"
 3793                «aˇ» = 1
 3794                b = 2
 3795
 3796                «const c:ˇ» usize = 3;
 3797            "},
 3798            window,
 3799            cx,
 3800        );
 3801
 3802        editor.tab(&Tab, window, cx);
 3803        assert_text_with_selections(
 3804            &mut editor,
 3805            indoc! {"
 3806                  «aˇ» = 1
 3807                b = 2
 3808
 3809                    «const c:ˇ» usize = 3;
 3810            "},
 3811            cx,
 3812        );
 3813        editor.backtab(&Backtab, window, cx);
 3814        assert_text_with_selections(
 3815            &mut editor,
 3816            indoc! {"
 3817                «aˇ» = 1
 3818                b = 2
 3819
 3820                «const c:ˇ» usize = 3;
 3821            "},
 3822            cx,
 3823        );
 3824
 3825        editor
 3826    });
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_backspace(cx: &mut TestAppContext) {
 3831    init_test(cx, |_| {});
 3832
 3833    let mut cx = EditorTestContext::new(cx).await;
 3834
 3835    // Basic backspace
 3836    cx.set_state(indoc! {"
 3837        onˇe two three
 3838        fou«rˇ» five six
 3839        seven «ˇeight nine
 3840        »ten
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844        oˇe two three
 3845        fouˇ five six
 3846        seven ˇten
 3847    "});
 3848
 3849    // Test backspace inside and around indents
 3850    cx.set_state(indoc! {"
 3851        zero
 3852            ˇone
 3853                ˇtwo
 3854            ˇ ˇ ˇ  three
 3855        ˇ  ˇ  four
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        zero
 3860        ˇone
 3861            ˇtwo
 3862        ˇ  threeˇ  four
 3863    "});
 3864}
 3865
 3866#[gpui::test]
 3867async fn test_delete(cx: &mut TestAppContext) {
 3868    init_test(cx, |_| {});
 3869
 3870    let mut cx = EditorTestContext::new(cx).await;
 3871    cx.set_state(indoc! {"
 3872        onˇe two three
 3873        fou«rˇ» five six
 3874        seven «ˇeight nine
 3875        »ten
 3876    "});
 3877    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3878    cx.assert_editor_state(indoc! {"
 3879        onˇ two three
 3880        fouˇ five six
 3881        seven ˇten
 3882    "});
 3883}
 3884
 3885#[gpui::test]
 3886fn test_delete_line(cx: &mut TestAppContext) {
 3887    init_test(cx, |_| {});
 3888
 3889    let editor = cx.add_window(|window, cx| {
 3890        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3891        build_editor(buffer, window, cx)
 3892    });
 3893    _ = editor.update(cx, |editor, window, cx| {
 3894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3895            s.select_display_ranges([
 3896                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3897                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3898                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3899            ])
 3900        });
 3901        editor.delete_line(&DeleteLine, window, cx);
 3902        assert_eq!(editor.display_text(cx), "ghi");
 3903        assert_eq!(
 3904            editor.selections.display_ranges(cx),
 3905            vec![
 3906                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3907                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3908            ]
 3909        );
 3910    });
 3911
 3912    let editor = cx.add_window(|window, cx| {
 3913        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3914        build_editor(buffer, window, cx)
 3915    });
 3916    _ = editor.update(cx, |editor, window, cx| {
 3917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3918            s.select_display_ranges([
 3919                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3920            ])
 3921        });
 3922        editor.delete_line(&DeleteLine, window, cx);
 3923        assert_eq!(editor.display_text(cx), "ghi\n");
 3924        assert_eq!(
 3925            editor.selections.display_ranges(cx),
 3926            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3927        );
 3928    });
 3929}
 3930
 3931#[gpui::test]
 3932fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3933    init_test(cx, |_| {});
 3934
 3935    cx.add_window(|window, cx| {
 3936        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3937        let mut editor = build_editor(buffer.clone(), window, cx);
 3938        let buffer = buffer.read(cx).as_singleton().unwrap();
 3939
 3940        assert_eq!(
 3941            editor.selections.ranges::<Point>(cx),
 3942            &[Point::new(0, 0)..Point::new(0, 0)]
 3943        );
 3944
 3945        // When on single line, replace newline at end by space
 3946        editor.join_lines(&JoinLines, window, cx);
 3947        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3948        assert_eq!(
 3949            editor.selections.ranges::<Point>(cx),
 3950            &[Point::new(0, 3)..Point::new(0, 3)]
 3951        );
 3952
 3953        // When multiple lines are selected, remove newlines that are spanned by the selection
 3954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3955            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3956        });
 3957        editor.join_lines(&JoinLines, window, cx);
 3958        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3959        assert_eq!(
 3960            editor.selections.ranges::<Point>(cx),
 3961            &[Point::new(0, 11)..Point::new(0, 11)]
 3962        );
 3963
 3964        // Undo should be transactional
 3965        editor.undo(&Undo, window, cx);
 3966        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3967        assert_eq!(
 3968            editor.selections.ranges::<Point>(cx),
 3969            &[Point::new(0, 5)..Point::new(2, 2)]
 3970        );
 3971
 3972        // When joining an empty line don't insert a space
 3973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3974            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3975        });
 3976        editor.join_lines(&JoinLines, window, cx);
 3977        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3978        assert_eq!(
 3979            editor.selections.ranges::<Point>(cx),
 3980            [Point::new(2, 3)..Point::new(2, 3)]
 3981        );
 3982
 3983        // We can remove trailing newlines
 3984        editor.join_lines(&JoinLines, window, cx);
 3985        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3986        assert_eq!(
 3987            editor.selections.ranges::<Point>(cx),
 3988            [Point::new(2, 3)..Point::new(2, 3)]
 3989        );
 3990
 3991        // We don't blow up on the last line
 3992        editor.join_lines(&JoinLines, window, cx);
 3993        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3994        assert_eq!(
 3995            editor.selections.ranges::<Point>(cx),
 3996            [Point::new(2, 3)..Point::new(2, 3)]
 3997        );
 3998
 3999        // reset to test indentation
 4000        editor.buffer.update(cx, |buffer, cx| {
 4001            buffer.edit(
 4002                [
 4003                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4004                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4005                ],
 4006                None,
 4007                cx,
 4008            )
 4009        });
 4010
 4011        // We remove any leading spaces
 4012        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4014            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4015        });
 4016        editor.join_lines(&JoinLines, window, cx);
 4017        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4018
 4019        // We don't insert a space for a line containing only spaces
 4020        editor.join_lines(&JoinLines, window, cx);
 4021        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4022
 4023        // We ignore any leading tabs
 4024        editor.join_lines(&JoinLines, window, cx);
 4025        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4026
 4027        editor
 4028    });
 4029}
 4030
 4031#[gpui::test]
 4032fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4033    init_test(cx, |_| {});
 4034
 4035    cx.add_window(|window, cx| {
 4036        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4037        let mut editor = build_editor(buffer.clone(), window, cx);
 4038        let buffer = buffer.read(cx).as_singleton().unwrap();
 4039
 4040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4041            s.select_ranges([
 4042                Point::new(0, 2)..Point::new(1, 1),
 4043                Point::new(1, 2)..Point::new(1, 2),
 4044                Point::new(3, 1)..Point::new(3, 2),
 4045            ])
 4046        });
 4047
 4048        editor.join_lines(&JoinLines, window, cx);
 4049        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4050
 4051        assert_eq!(
 4052            editor.selections.ranges::<Point>(cx),
 4053            [
 4054                Point::new(0, 7)..Point::new(0, 7),
 4055                Point::new(1, 3)..Point::new(1, 3)
 4056            ]
 4057        );
 4058        editor
 4059    });
 4060}
 4061
 4062#[gpui::test]
 4063async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4064    init_test(cx, |_| {});
 4065
 4066    let mut cx = EditorTestContext::new(cx).await;
 4067
 4068    let diff_base = r#"
 4069        Line 0
 4070        Line 1
 4071        Line 2
 4072        Line 3
 4073        "#
 4074    .unindent();
 4075
 4076    cx.set_state(
 4077        &r#"
 4078        ˇLine 0
 4079        Line 1
 4080        Line 2
 4081        Line 3
 4082        "#
 4083        .unindent(),
 4084    );
 4085
 4086    cx.set_head_text(&diff_base);
 4087    executor.run_until_parked();
 4088
 4089    // Join lines
 4090    cx.update_editor(|editor, window, cx| {
 4091        editor.join_lines(&JoinLines, window, cx);
 4092    });
 4093    executor.run_until_parked();
 4094
 4095    cx.assert_editor_state(
 4096        &r#"
 4097        Line 0ˇ Line 1
 4098        Line 2
 4099        Line 3
 4100        "#
 4101        .unindent(),
 4102    );
 4103    // Join again
 4104    cx.update_editor(|editor, window, cx| {
 4105        editor.join_lines(&JoinLines, window, cx);
 4106    });
 4107    executor.run_until_parked();
 4108
 4109    cx.assert_editor_state(
 4110        &r#"
 4111        Line 0 Line 1ˇ Line 2
 4112        Line 3
 4113        "#
 4114        .unindent(),
 4115    );
 4116}
 4117
 4118#[gpui::test]
 4119async fn test_custom_newlines_cause_no_false_positive_diffs(
 4120    executor: BackgroundExecutor,
 4121    cx: &mut TestAppContext,
 4122) {
 4123    init_test(cx, |_| {});
 4124    let mut cx = EditorTestContext::new(cx).await;
 4125    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4126    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4127    executor.run_until_parked();
 4128
 4129    cx.update_editor(|editor, window, cx| {
 4130        let snapshot = editor.snapshot(window, cx);
 4131        assert_eq!(
 4132            snapshot
 4133                .buffer_snapshot
 4134                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4135                .collect::<Vec<_>>(),
 4136            Vec::new(),
 4137            "Should not have any diffs for files with custom newlines"
 4138        );
 4139    });
 4140}
 4141
 4142#[gpui::test]
 4143async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4144    init_test(cx, |_| {});
 4145
 4146    let mut cx = EditorTestContext::new(cx).await;
 4147
 4148    // Test sort_lines_case_insensitive()
 4149    cx.set_state(indoc! {"
 4150        «z
 4151        y
 4152        x
 4153        Z
 4154        Y
 4155        Xˇ»
 4156    "});
 4157    cx.update_editor(|e, window, cx| {
 4158        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4159    });
 4160    cx.assert_editor_state(indoc! {"
 4161        «x
 4162        X
 4163        y
 4164        Y
 4165        z
 4166        Zˇ»
 4167    "});
 4168
 4169    // Test sort_lines_by_length()
 4170    //
 4171    // Demonstrates:
 4172    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4173    // - sort is stable
 4174    cx.set_state(indoc! {"
 4175        «123
 4176        æ
 4177        12
 4178 4179        1
 4180        æˇ»
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        «æ
 4185 4186        1
 4187        æ
 4188        12
 4189        123ˇ»
 4190    "});
 4191
 4192    // Test reverse_lines()
 4193    cx.set_state(indoc! {"
 4194        «5
 4195        4
 4196        3
 4197        2
 4198        1ˇ»
 4199    "});
 4200    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4201    cx.assert_editor_state(indoc! {"
 4202        «1
 4203        2
 4204        3
 4205        4
 4206        5ˇ»
 4207    "});
 4208
 4209    // Skip testing shuffle_line()
 4210
 4211    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4212    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4213
 4214    // Don't manipulate when cursor is on single line, but expand the selection
 4215    cx.set_state(indoc! {"
 4216        ddˇdd
 4217        ccc
 4218        bb
 4219        a
 4220    "});
 4221    cx.update_editor(|e, window, cx| {
 4222        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4223    });
 4224    cx.assert_editor_state(indoc! {"
 4225        «ddddˇ»
 4226        ccc
 4227        bb
 4228        a
 4229    "});
 4230
 4231    // Basic manipulate case
 4232    // Start selection moves to column 0
 4233    // End of selection shrinks to fit shorter line
 4234    cx.set_state(indoc! {"
 4235        dd«d
 4236        ccc
 4237        bb
 4238        aaaaaˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4242    });
 4243    cx.assert_editor_state(indoc! {"
 4244        «aaaaa
 4245        bb
 4246        ccc
 4247        dddˇ»
 4248    "});
 4249
 4250    // Manipulate case with newlines
 4251    cx.set_state(indoc! {"
 4252        dd«d
 4253        ccc
 4254
 4255        bb
 4256        aaaaa
 4257
 4258        ˇ»
 4259    "});
 4260    cx.update_editor(|e, window, cx| {
 4261        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4262    });
 4263    cx.assert_editor_state(indoc! {"
 4264        «
 4265
 4266        aaaaa
 4267        bb
 4268        ccc
 4269        dddˇ»
 4270
 4271    "});
 4272
 4273    // Adding new line
 4274    cx.set_state(indoc! {"
 4275        aa«a
 4276        bbˇ»b
 4277    "});
 4278    cx.update_editor(|e, window, cx| {
 4279        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4280    });
 4281    cx.assert_editor_state(indoc! {"
 4282        «aaa
 4283        bbb
 4284        added_lineˇ»
 4285    "});
 4286
 4287    // Removing line
 4288    cx.set_state(indoc! {"
 4289        aa«a
 4290        bbbˇ»
 4291    "});
 4292    cx.update_editor(|e, window, cx| {
 4293        e.manipulate_immutable_lines(window, cx, |lines| {
 4294            lines.pop();
 4295        })
 4296    });
 4297    cx.assert_editor_state(indoc! {"
 4298        «aaaˇ»
 4299    "});
 4300
 4301    // Removing all lines
 4302    cx.set_state(indoc! {"
 4303        aa«a
 4304        bbbˇ»
 4305    "});
 4306    cx.update_editor(|e, window, cx| {
 4307        e.manipulate_immutable_lines(window, cx, |lines| {
 4308            lines.drain(..);
 4309        })
 4310    });
 4311    cx.assert_editor_state(indoc! {"
 4312        ˇ
 4313    "});
 4314}
 4315
 4316#[gpui::test]
 4317async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4318    init_test(cx, |_| {});
 4319
 4320    let mut cx = EditorTestContext::new(cx).await;
 4321
 4322    // Consider continuous selection as single selection
 4323    cx.set_state(indoc! {"
 4324        Aaa«aa
 4325        cˇ»c«c
 4326        bb
 4327        aaaˇ»aa
 4328    "});
 4329    cx.update_editor(|e, window, cx| {
 4330        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4331    });
 4332    cx.assert_editor_state(indoc! {"
 4333        «Aaaaa
 4334        ccc
 4335        bb
 4336        aaaaaˇ»
 4337    "});
 4338
 4339    cx.set_state(indoc! {"
 4340        Aaa«aa
 4341        cˇ»c«c
 4342        bb
 4343        aaaˇ»aa
 4344    "});
 4345    cx.update_editor(|e, window, cx| {
 4346        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4347    });
 4348    cx.assert_editor_state(indoc! {"
 4349        «Aaaaa
 4350        ccc
 4351        bbˇ»
 4352    "});
 4353
 4354    // Consider non continuous selection as distinct dedup operations
 4355    cx.set_state(indoc! {"
 4356        «aaaaa
 4357        bb
 4358        aaaaa
 4359        aaaaaˇ»
 4360
 4361        aaa«aaˇ»
 4362    "});
 4363    cx.update_editor(|e, window, cx| {
 4364        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4365    });
 4366    cx.assert_editor_state(indoc! {"
 4367        «aaaaa
 4368        bbˇ»
 4369
 4370        «aaaaaˇ»
 4371    "});
 4372}
 4373
 4374#[gpui::test]
 4375async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4376    init_test(cx, |_| {});
 4377
 4378    let mut cx = EditorTestContext::new(cx).await;
 4379
 4380    cx.set_state(indoc! {"
 4381        «Aaa
 4382        aAa
 4383        Aaaˇ»
 4384    "});
 4385    cx.update_editor(|e, window, cx| {
 4386        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4387    });
 4388    cx.assert_editor_state(indoc! {"
 4389        «Aaa
 4390        aAaˇ»
 4391    "});
 4392
 4393    cx.set_state(indoc! {"
 4394        «Aaa
 4395        aAa
 4396        aaAˇ»
 4397    "});
 4398    cx.update_editor(|e, window, cx| {
 4399        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4400    });
 4401    cx.assert_editor_state(indoc! {"
 4402        «Aaaˇ»
 4403    "});
 4404}
 4405
 4406#[gpui::test]
 4407async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4408    init_test(cx, |_| {});
 4409
 4410    let mut cx = EditorTestContext::new(cx).await;
 4411
 4412    // Manipulate with multiple selections on a single line
 4413    cx.set_state(indoc! {"
 4414        dd«dd
 4415        cˇ»c«c
 4416        bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «aaaaa
 4424        bb
 4425        ccc
 4426        ddddˇ»
 4427    "});
 4428
 4429    // Manipulate with multiple disjoin selections
 4430    cx.set_state(indoc! {"
 4431 4432        4
 4433        3
 4434        2
 4435        1ˇ»
 4436
 4437        dd«dd
 4438        ccc
 4439        bb
 4440        aaaˇ»aa
 4441    "});
 4442    cx.update_editor(|e, window, cx| {
 4443        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «1
 4447        2
 4448        3
 4449        4
 4450        5ˇ»
 4451
 4452        «aaaaa
 4453        bb
 4454        ccc
 4455        ddddˇ»
 4456    "});
 4457
 4458    // Adding lines on each selection
 4459    cx.set_state(indoc! {"
 4460 4461        1ˇ»
 4462
 4463        bb«bb
 4464        aaaˇ»aa
 4465    "});
 4466    cx.update_editor(|e, window, cx| {
 4467        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4468    });
 4469    cx.assert_editor_state(indoc! {"
 4470        «2
 4471        1
 4472        added lineˇ»
 4473
 4474        «bbbb
 4475        aaaaa
 4476        added lineˇ»
 4477    "});
 4478
 4479    // Removing lines on each selection
 4480    cx.set_state(indoc! {"
 4481 4482        1ˇ»
 4483
 4484        bb«bb
 4485        aaaˇ»aa
 4486    "});
 4487    cx.update_editor(|e, window, cx| {
 4488        e.manipulate_immutable_lines(window, cx, |lines| {
 4489            lines.pop();
 4490        })
 4491    });
 4492    cx.assert_editor_state(indoc! {"
 4493        «2ˇ»
 4494
 4495        «bbbbˇ»
 4496    "});
 4497}
 4498
 4499#[gpui::test]
 4500async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4501    init_test(cx, |settings| {
 4502        settings.defaults.tab_size = NonZeroU32::new(3)
 4503    });
 4504
 4505    let mut cx = EditorTestContext::new(cx).await;
 4506
 4507    // MULTI SELECTION
 4508    // Ln.1 "«" tests empty lines
 4509    // Ln.9 tests just leading whitespace
 4510    cx.set_state(indoc! {"
 4511        «
 4512        abc                 // No indentationˇ»
 4513        «\tabc              // 1 tabˇ»
 4514        \t\tabc «      ˇ»   // 2 tabs
 4515        \t ab«c             // Tab followed by space
 4516         \tabc              // Space followed by tab (3 spaces should be the result)
 4517        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4518           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4519        \t
 4520        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4521    "});
 4522    cx.update_editor(|e, window, cx| {
 4523        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4524    });
 4525    cx.assert_editor_state(
 4526        indoc! {"
 4527            «
 4528            abc                 // No indentation
 4529               abc              // 1 tab
 4530                  abc          // 2 tabs
 4531                abc             // Tab followed by space
 4532               abc              // Space followed by tab (3 spaces should be the result)
 4533                           abc   // Mixed indentation (tab conversion depends on the column)
 4534               abc         // Already space indented
 4535               ·
 4536               abc\tdef          // Only the leading tab is manipulatedˇ»
 4537        "}
 4538        .replace("·", "")
 4539        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4540    );
 4541
 4542    // Test on just a few lines, the others should remain unchanged
 4543    // Only lines (3, 5, 10, 11) should change
 4544    cx.set_state(
 4545        indoc! {"
 4546            ·
 4547            abc                 // No indentation
 4548            \tabcˇ               // 1 tab
 4549            \t\tabc             // 2 tabs
 4550            \t abcˇ              // Tab followed by space
 4551             \tabc              // Space followed by tab (3 spaces should be the result)
 4552            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4553               abc              // Already space indented
 4554            «\t
 4555            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4556        "}
 4557        .replace("·", "")
 4558        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4559    );
 4560    cx.update_editor(|e, window, cx| {
 4561        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4562    });
 4563    cx.assert_editor_state(
 4564        indoc! {"
 4565            ·
 4566            abc                 // No indentation
 4567            «   abc               // 1 tabˇ»
 4568            \t\tabc             // 2 tabs
 4569            «    abc              // Tab followed by spaceˇ»
 4570             \tabc              // Space followed by tab (3 spaces should be the result)
 4571            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4572               abc              // Already space indented
 4573            «   ·
 4574               abc\tdef          // Only the leading tab is manipulatedˇ»
 4575        "}
 4576        .replace("·", "")
 4577        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4578    );
 4579
 4580    // SINGLE SELECTION
 4581    // Ln.1 "«" tests empty lines
 4582    // Ln.9 tests just leading whitespace
 4583    cx.set_state(indoc! {"
 4584        «
 4585        abc                 // No indentation
 4586        \tabc               // 1 tab
 4587        \t\tabc             // 2 tabs
 4588        \t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (3 spaces should be the result)
 4590        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4591           abc              // Already space indented
 4592        \t
 4593        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4594    "});
 4595    cx.update_editor(|e, window, cx| {
 4596        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4597    });
 4598    cx.assert_editor_state(
 4599        indoc! {"
 4600            «
 4601            abc                 // No indentation
 4602               abc               // 1 tab
 4603                  abc             // 2 tabs
 4604                abc              // Tab followed by space
 4605               abc              // Space followed by tab (3 spaces should be the result)
 4606                           abc   // Mixed indentation (tab conversion depends on the column)
 4607               abc              // Already space indented
 4608               ·
 4609               abc\tdef          // Only the leading tab is manipulatedˇ»
 4610        "}
 4611        .replace("·", "")
 4612        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4613    );
 4614}
 4615
 4616#[gpui::test]
 4617async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4618    init_test(cx, |settings| {
 4619        settings.defaults.tab_size = NonZeroU32::new(3)
 4620    });
 4621
 4622    let mut cx = EditorTestContext::new(cx).await;
 4623
 4624    // MULTI SELECTION
 4625    // Ln.1 "«" tests empty lines
 4626    // Ln.11 tests just leading whitespace
 4627    cx.set_state(indoc! {"
 4628        «
 4629        abˇ»ˇc                 // No indentation
 4630         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4631          abc  «             // 2 spaces (< 3 so dont convert)
 4632           abc              // 3 spaces (convert)
 4633             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4634        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4635        «\t abc              // Tab followed by space
 4636         \tabc              // Space followed by tab (should be consumed due to tab)
 4637        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4638           \tˇ»  «\t
 4639           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4640    "});
 4641    cx.update_editor(|e, window, cx| {
 4642        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4643    });
 4644    cx.assert_editor_state(indoc! {"
 4645        «
 4646        abc                 // No indentation
 4647         abc                // 1 space (< 3 so dont convert)
 4648          abc               // 2 spaces (< 3 so dont convert)
 4649        \tabc              // 3 spaces (convert)
 4650        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4651        \t\t\tabc           // Already tab indented
 4652        \t abc              // Tab followed by space
 4653        \tabc              // Space followed by tab (should be consumed due to tab)
 4654        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4655        \t\t\t
 4656        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4657    "});
 4658
 4659    // Test on just a few lines, the other should remain unchanged
 4660    // Only lines (4, 8, 11, 12) should change
 4661    cx.set_state(
 4662        indoc! {"
 4663            ·
 4664            abc                 // No indentation
 4665             abc                // 1 space (< 3 so dont convert)
 4666              abc               // 2 spaces (< 3 so dont convert)
 4667            «   abc              // 3 spaces (convert)ˇ»
 4668                 abc            // 5 spaces (1 tab + 2 spaces)
 4669            \t\t\tabc           // Already tab indented
 4670            \t abc              // Tab followed by space
 4671             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4672               \t\t  \tabc      // Mixed indentation
 4673            \t \t  \t   \tabc   // Mixed indentation
 4674               \t  \tˇ
 4675            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4676        "}
 4677        .replace("·", "")
 4678        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4679    );
 4680    cx.update_editor(|e, window, cx| {
 4681        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4682    });
 4683    cx.assert_editor_state(
 4684        indoc! {"
 4685            ·
 4686            abc                 // No indentation
 4687             abc                // 1 space (< 3 so dont convert)
 4688              abc               // 2 spaces (< 3 so dont convert)
 4689            «\tabc              // 3 spaces (convert)ˇ»
 4690                 abc            // 5 spaces (1 tab + 2 spaces)
 4691            \t\t\tabc           // Already tab indented
 4692            \t abc              // Tab followed by space
 4693            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4694               \t\t  \tabc      // Mixed indentation
 4695            \t \t  \t   \tabc   // Mixed indentation
 4696            «\t\t\t
 4697            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4698        "}
 4699        .replace("·", "")
 4700        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4701    );
 4702
 4703    // SINGLE SELECTION
 4704    // Ln.1 "«" tests empty lines
 4705    // Ln.11 tests just leading whitespace
 4706    cx.set_state(indoc! {"
 4707        «
 4708        abc                 // No indentation
 4709         abc                // 1 space (< 3 so dont convert)
 4710          abc               // 2 spaces (< 3 so dont convert)
 4711           abc              // 3 spaces (convert)
 4712             abc            // 5 spaces (1 tab + 2 spaces)
 4713        \t\t\tabc           // Already tab indented
 4714        \t abc              // Tab followed by space
 4715         \tabc              // Space followed by tab (should be consumed due to tab)
 4716        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4717           \t  \t
 4718           abc   \t         // Only the leading spaces should be convertedˇ»
 4719    "});
 4720    cx.update_editor(|e, window, cx| {
 4721        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4722    });
 4723    cx.assert_editor_state(indoc! {"
 4724        «
 4725        abc                 // No indentation
 4726         abc                // 1 space (< 3 so dont convert)
 4727          abc               // 2 spaces (< 3 so dont convert)
 4728        \tabc              // 3 spaces (convert)
 4729        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4730        \t\t\tabc           // Already tab indented
 4731        \t abc              // Tab followed by space
 4732        \tabc              // Space followed by tab (should be consumed due to tab)
 4733        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4734        \t\t\t
 4735        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4736    "});
 4737}
 4738
 4739#[gpui::test]
 4740async fn test_toggle_case(cx: &mut TestAppContext) {
 4741    init_test(cx, |_| {});
 4742
 4743    let mut cx = EditorTestContext::new(cx).await;
 4744
 4745    // If all lower case -> upper case
 4746    cx.set_state(indoc! {"
 4747        «hello worldˇ»
 4748    "});
 4749    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4750    cx.assert_editor_state(indoc! {"
 4751        «HELLO WORLDˇ»
 4752    "});
 4753
 4754    // If all upper case -> lower case
 4755    cx.set_state(indoc! {"
 4756        «HELLO WORLDˇ»
 4757    "});
 4758    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4759    cx.assert_editor_state(indoc! {"
 4760        «hello worldˇ»
 4761    "});
 4762
 4763    // If any upper case characters are identified -> lower case
 4764    // This matches JetBrains IDEs
 4765    cx.set_state(indoc! {"
 4766        «hEllo worldˇ»
 4767    "});
 4768    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4769    cx.assert_editor_state(indoc! {"
 4770        «hello worldˇ»
 4771    "});
 4772}
 4773
 4774#[gpui::test]
 4775async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4776    init_test(cx, |_| {});
 4777
 4778    let mut cx = EditorTestContext::new(cx).await;
 4779
 4780    cx.set_state(indoc! {"
 4781        «implement-windows-supportˇ»
 4782    "});
 4783    cx.update_editor(|e, window, cx| {
 4784        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4785    });
 4786    cx.assert_editor_state(indoc! {"
 4787        «Implement windows supportˇ»
 4788    "});
 4789}
 4790
 4791#[gpui::test]
 4792async fn test_manipulate_text(cx: &mut TestAppContext) {
 4793    init_test(cx, |_| {});
 4794
 4795    let mut cx = EditorTestContext::new(cx).await;
 4796
 4797    // Test convert_to_upper_case()
 4798    cx.set_state(indoc! {"
 4799        «hello worldˇ»
 4800    "});
 4801    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4802    cx.assert_editor_state(indoc! {"
 4803        «HELLO WORLDˇ»
 4804    "});
 4805
 4806    // Test convert_to_lower_case()
 4807    cx.set_state(indoc! {"
 4808        «HELLO WORLDˇ»
 4809    "});
 4810    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4811    cx.assert_editor_state(indoc! {"
 4812        «hello worldˇ»
 4813    "});
 4814
 4815    // Test multiple line, single selection case
 4816    cx.set_state(indoc! {"
 4817        «The quick brown
 4818        fox jumps over
 4819        the lazy dogˇ»
 4820    "});
 4821    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4822    cx.assert_editor_state(indoc! {"
 4823        «The Quick Brown
 4824        Fox Jumps Over
 4825        The Lazy Dogˇ»
 4826    "});
 4827
 4828    // Test multiple line, single selection case
 4829    cx.set_state(indoc! {"
 4830        «The quick brown
 4831        fox jumps over
 4832        the lazy dogˇ»
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «TheQuickBrown
 4839        FoxJumpsOver
 4840        TheLazyDogˇ»
 4841    "});
 4842
 4843    // From here on out, test more complex cases of manipulate_text()
 4844
 4845    // Test no selection case - should affect words cursors are in
 4846    // Cursor at beginning, middle, and end of word
 4847    cx.set_state(indoc! {"
 4848        ˇhello big beauˇtiful worldˇ
 4849    "});
 4850    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4851    cx.assert_editor_state(indoc! {"
 4852        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4853    "});
 4854
 4855    // Test multiple selections on a single line and across multiple lines
 4856    cx.set_state(indoc! {"
 4857        «Theˇ» quick «brown
 4858        foxˇ» jumps «overˇ»
 4859        the «lazyˇ» dog
 4860    "});
 4861    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4862    cx.assert_editor_state(indoc! {"
 4863        «THEˇ» quick «BROWN
 4864        FOXˇ» jumps «OVERˇ»
 4865        the «LAZYˇ» dog
 4866    "});
 4867
 4868    // Test case where text length grows
 4869    cx.set_state(indoc! {"
 4870        «tschüߡ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «TSCHÜSSˇ»
 4875    "});
 4876
 4877    // Test to make sure we don't crash when text shrinks
 4878    cx.set_state(indoc! {"
 4879        aaa_bbbˇ
 4880    "});
 4881    cx.update_editor(|e, window, cx| {
 4882        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4883    });
 4884    cx.assert_editor_state(indoc! {"
 4885        «aaaBbbˇ»
 4886    "});
 4887
 4888    // Test to make sure we all aware of the fact that each word can grow and shrink
 4889    // Final selections should be aware of this fact
 4890    cx.set_state(indoc! {"
 4891        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4892    "});
 4893    cx.update_editor(|e, window, cx| {
 4894        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4895    });
 4896    cx.assert_editor_state(indoc! {"
 4897        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4898    "});
 4899
 4900    cx.set_state(indoc! {"
 4901        «hElLo, WoRld!ˇ»
 4902    "});
 4903    cx.update_editor(|e, window, cx| {
 4904        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4905    });
 4906    cx.assert_editor_state(indoc! {"
 4907        «HeLlO, wOrLD!ˇ»
 4908    "});
 4909}
 4910
 4911#[gpui::test]
 4912fn test_duplicate_line(cx: &mut TestAppContext) {
 4913    init_test(cx, |_| {});
 4914
 4915    let editor = cx.add_window(|window, cx| {
 4916        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4917        build_editor(buffer, window, cx)
 4918    });
 4919    _ = editor.update(cx, |editor, window, cx| {
 4920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4921            s.select_display_ranges([
 4922                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4923                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4924                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4925                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4926            ])
 4927        });
 4928        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4929        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4930        assert_eq!(
 4931            editor.selections.display_ranges(cx),
 4932            vec![
 4933                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4934                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4935                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4936                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4937            ]
 4938        );
 4939    });
 4940
 4941    let editor = cx.add_window(|window, cx| {
 4942        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4943        build_editor(buffer, window, cx)
 4944    });
 4945    _ = editor.update(cx, |editor, window, cx| {
 4946        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4947            s.select_display_ranges([
 4948                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4949                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4950            ])
 4951        });
 4952        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4953        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4954        assert_eq!(
 4955            editor.selections.display_ranges(cx),
 4956            vec![
 4957                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4958                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4959            ]
 4960        );
 4961    });
 4962
 4963    // With `move_upwards` the selections stay in place, except for
 4964    // the lines inserted above them
 4965    let editor = cx.add_window(|window, cx| {
 4966        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4967        build_editor(buffer, window, cx)
 4968    });
 4969    _ = editor.update(cx, |editor, window, cx| {
 4970        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4971            s.select_display_ranges([
 4972                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4973                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4974                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4975                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4976            ])
 4977        });
 4978        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4979        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4980        assert_eq!(
 4981            editor.selections.display_ranges(cx),
 4982            vec![
 4983                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4984                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4985                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4986                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4987            ]
 4988        );
 4989    });
 4990
 4991    let editor = cx.add_window(|window, cx| {
 4992        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4993        build_editor(buffer, window, cx)
 4994    });
 4995    _ = editor.update(cx, |editor, window, cx| {
 4996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4997            s.select_display_ranges([
 4998                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4999                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5000            ])
 5001        });
 5002        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5003        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5004        assert_eq!(
 5005            editor.selections.display_ranges(cx),
 5006            vec![
 5007                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5008                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5009            ]
 5010        );
 5011    });
 5012
 5013    let editor = cx.add_window(|window, cx| {
 5014        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5015        build_editor(buffer, window, cx)
 5016    });
 5017    _ = editor.update(cx, |editor, window, cx| {
 5018        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5019            s.select_display_ranges([
 5020                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5021                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5022            ])
 5023        });
 5024        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5025        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5031            ]
 5032        );
 5033    });
 5034}
 5035
 5036#[gpui::test]
 5037fn test_move_line_up_down(cx: &mut TestAppContext) {
 5038    init_test(cx, |_| {});
 5039
 5040    let editor = cx.add_window(|window, cx| {
 5041        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5042        build_editor(buffer, window, cx)
 5043    });
 5044    _ = editor.update(cx, |editor, window, cx| {
 5045        editor.fold_creases(
 5046            vec![
 5047                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5048                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5049                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5050            ],
 5051            true,
 5052            window,
 5053            cx,
 5054        );
 5055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5056            s.select_display_ranges([
 5057                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5058                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5059                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5060                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5061            ])
 5062        });
 5063        assert_eq!(
 5064            editor.display_text(cx),
 5065            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5066        );
 5067
 5068        editor.move_line_up(&MoveLineUp, window, cx);
 5069        assert_eq!(
 5070            editor.display_text(cx),
 5071            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5072        );
 5073        assert_eq!(
 5074            editor.selections.display_ranges(cx),
 5075            vec![
 5076                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5077                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5078                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5079                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5080            ]
 5081        );
 5082    });
 5083
 5084    _ = editor.update(cx, |editor, window, cx| {
 5085        editor.move_line_down(&MoveLineDown, window, cx);
 5086        assert_eq!(
 5087            editor.display_text(cx),
 5088            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5089        );
 5090        assert_eq!(
 5091            editor.selections.display_ranges(cx),
 5092            vec![
 5093                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5094                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5095                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5096                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5097            ]
 5098        );
 5099    });
 5100
 5101    _ = editor.update(cx, |editor, window, cx| {
 5102        editor.move_line_down(&MoveLineDown, window, cx);
 5103        assert_eq!(
 5104            editor.display_text(cx),
 5105            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5106        );
 5107        assert_eq!(
 5108            editor.selections.display_ranges(cx),
 5109            vec![
 5110                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5111                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5112                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5113                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5114            ]
 5115        );
 5116    });
 5117
 5118    _ = editor.update(cx, |editor, window, cx| {
 5119        editor.move_line_up(&MoveLineUp, window, cx);
 5120        assert_eq!(
 5121            editor.display_text(cx),
 5122            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5123        );
 5124        assert_eq!(
 5125            editor.selections.display_ranges(cx),
 5126            vec![
 5127                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5128                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5129                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5130                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5131            ]
 5132        );
 5133    });
 5134}
 5135
 5136#[gpui::test]
 5137fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5138    init_test(cx, |_| {});
 5139    let editor = cx.add_window(|window, cx| {
 5140        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5141        build_editor(buffer, window, cx)
 5142    });
 5143    _ = editor.update(cx, |editor, window, cx| {
 5144        editor.fold_creases(
 5145            vec![Crease::simple(
 5146                Point::new(6, 4)..Point::new(7, 4),
 5147                FoldPlaceholder::test(),
 5148            )],
 5149            true,
 5150            window,
 5151            cx,
 5152        );
 5153        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5154            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5155        });
 5156        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5157        editor.move_line_up(&MoveLineUp, window, cx);
 5158        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5159        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5160    });
 5161}
 5162
 5163#[gpui::test]
 5164fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5165    init_test(cx, |_| {});
 5166
 5167    let editor = cx.add_window(|window, cx| {
 5168        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5169        build_editor(buffer, window, cx)
 5170    });
 5171    _ = editor.update(cx, |editor, window, cx| {
 5172        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5173        editor.insert_blocks(
 5174            [BlockProperties {
 5175                style: BlockStyle::Fixed,
 5176                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5177                height: Some(1),
 5178                render: Arc::new(|_| div().into_any()),
 5179                priority: 0,
 5180            }],
 5181            Some(Autoscroll::fit()),
 5182            cx,
 5183        );
 5184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5185            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5186        });
 5187        editor.move_line_down(&MoveLineDown, window, cx);
 5188    });
 5189}
 5190
 5191#[gpui::test]
 5192async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5193    init_test(cx, |_| {});
 5194
 5195    let mut cx = EditorTestContext::new(cx).await;
 5196    cx.set_state(
 5197        &"
 5198            ˇzero
 5199            one
 5200            two
 5201            three
 5202            four
 5203            five
 5204        "
 5205        .unindent(),
 5206    );
 5207
 5208    // Create a four-line block that replaces three lines of text.
 5209    cx.update_editor(|editor, window, cx| {
 5210        let snapshot = editor.snapshot(window, cx);
 5211        let snapshot = &snapshot.buffer_snapshot;
 5212        let placement = BlockPlacement::Replace(
 5213            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5214        );
 5215        editor.insert_blocks(
 5216            [BlockProperties {
 5217                placement,
 5218                height: Some(4),
 5219                style: BlockStyle::Sticky,
 5220                render: Arc::new(|_| gpui::div().into_any_element()),
 5221                priority: 0,
 5222            }],
 5223            None,
 5224            cx,
 5225        );
 5226    });
 5227
 5228    // Move down so that the cursor touches the block.
 5229    cx.update_editor(|editor, window, cx| {
 5230        editor.move_down(&Default::default(), window, cx);
 5231    });
 5232    cx.assert_editor_state(
 5233        &"
 5234            zero
 5235            «one
 5236            two
 5237            threeˇ»
 5238            four
 5239            five
 5240        "
 5241        .unindent(),
 5242    );
 5243
 5244    // Move down past the block.
 5245    cx.update_editor(|editor, window, cx| {
 5246        editor.move_down(&Default::default(), window, cx);
 5247    });
 5248    cx.assert_editor_state(
 5249        &"
 5250            zero
 5251            one
 5252            two
 5253            three
 5254            ˇfour
 5255            five
 5256        "
 5257        .unindent(),
 5258    );
 5259}
 5260
 5261#[gpui::test]
 5262fn test_transpose(cx: &mut TestAppContext) {
 5263    init_test(cx, |_| {});
 5264
 5265    _ = cx.add_window(|window, cx| {
 5266        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5267        editor.set_style(EditorStyle::default(), window, cx);
 5268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5269            s.select_ranges([1..1])
 5270        });
 5271        editor.transpose(&Default::default(), window, cx);
 5272        assert_eq!(editor.text(cx), "bac");
 5273        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5274
 5275        editor.transpose(&Default::default(), window, cx);
 5276        assert_eq!(editor.text(cx), "bca");
 5277        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5278
 5279        editor.transpose(&Default::default(), window, cx);
 5280        assert_eq!(editor.text(cx), "bac");
 5281        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5282
 5283        editor
 5284    });
 5285
 5286    _ = cx.add_window(|window, cx| {
 5287        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5288        editor.set_style(EditorStyle::default(), window, cx);
 5289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5290            s.select_ranges([3..3])
 5291        });
 5292        editor.transpose(&Default::default(), window, cx);
 5293        assert_eq!(editor.text(cx), "acb\nde");
 5294        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5295
 5296        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5297            s.select_ranges([4..4])
 5298        });
 5299        editor.transpose(&Default::default(), window, cx);
 5300        assert_eq!(editor.text(cx), "acbd\ne");
 5301        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5302
 5303        editor.transpose(&Default::default(), window, cx);
 5304        assert_eq!(editor.text(cx), "acbde\n");
 5305        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5306
 5307        editor.transpose(&Default::default(), window, cx);
 5308        assert_eq!(editor.text(cx), "acbd\ne");
 5309        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5310
 5311        editor
 5312    });
 5313
 5314    _ = cx.add_window(|window, cx| {
 5315        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5316        editor.set_style(EditorStyle::default(), window, cx);
 5317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5318            s.select_ranges([1..1, 2..2, 4..4])
 5319        });
 5320        editor.transpose(&Default::default(), window, cx);
 5321        assert_eq!(editor.text(cx), "bacd\ne");
 5322        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5323
 5324        editor.transpose(&Default::default(), window, cx);
 5325        assert_eq!(editor.text(cx), "bcade\n");
 5326        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5327
 5328        editor.transpose(&Default::default(), window, cx);
 5329        assert_eq!(editor.text(cx), "bcda\ne");
 5330        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5331
 5332        editor.transpose(&Default::default(), window, cx);
 5333        assert_eq!(editor.text(cx), "bcade\n");
 5334        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5335
 5336        editor.transpose(&Default::default(), window, cx);
 5337        assert_eq!(editor.text(cx), "bcaed\n");
 5338        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5339
 5340        editor
 5341    });
 5342
 5343    _ = cx.add_window(|window, cx| {
 5344        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5345        editor.set_style(EditorStyle::default(), window, cx);
 5346        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5347            s.select_ranges([4..4])
 5348        });
 5349        editor.transpose(&Default::default(), window, cx);
 5350        assert_eq!(editor.text(cx), "🏀🍐✋");
 5351        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5352
 5353        editor.transpose(&Default::default(), window, cx);
 5354        assert_eq!(editor.text(cx), "🏀✋🍐");
 5355        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5356
 5357        editor.transpose(&Default::default(), window, cx);
 5358        assert_eq!(editor.text(cx), "🏀🍐✋");
 5359        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5360
 5361        editor
 5362    });
 5363}
 5364
 5365#[gpui::test]
 5366async fn test_rewrap(cx: &mut TestAppContext) {
 5367    init_test(cx, |settings| {
 5368        settings.languages.0.extend([
 5369            (
 5370                "Markdown".into(),
 5371                LanguageSettingsContent {
 5372                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5373                    preferred_line_length: Some(40),
 5374                    ..Default::default()
 5375                },
 5376            ),
 5377            (
 5378                "Plain Text".into(),
 5379                LanguageSettingsContent {
 5380                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5381                    preferred_line_length: Some(40),
 5382                    ..Default::default()
 5383                },
 5384            ),
 5385            (
 5386                "C++".into(),
 5387                LanguageSettingsContent {
 5388                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5389                    preferred_line_length: Some(40),
 5390                    ..Default::default()
 5391                },
 5392            ),
 5393            (
 5394                "Python".into(),
 5395                LanguageSettingsContent {
 5396                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5397                    preferred_line_length: Some(40),
 5398                    ..Default::default()
 5399                },
 5400            ),
 5401            (
 5402                "Rust".into(),
 5403                LanguageSettingsContent {
 5404                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5405                    preferred_line_length: Some(40),
 5406                    ..Default::default()
 5407                },
 5408            ),
 5409        ])
 5410    });
 5411
 5412    let mut cx = EditorTestContext::new(cx).await;
 5413
 5414    let cpp_language = Arc::new(Language::new(
 5415        LanguageConfig {
 5416            name: "C++".into(),
 5417            line_comments: vec!["// ".into()],
 5418            ..LanguageConfig::default()
 5419        },
 5420        None,
 5421    ));
 5422    let python_language = Arc::new(Language::new(
 5423        LanguageConfig {
 5424            name: "Python".into(),
 5425            line_comments: vec!["# ".into()],
 5426            ..LanguageConfig::default()
 5427        },
 5428        None,
 5429    ));
 5430    let markdown_language = Arc::new(Language::new(
 5431        LanguageConfig {
 5432            name: "Markdown".into(),
 5433            rewrap_prefixes: vec![
 5434                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5435                regex::Regex::new("[-*+]\\s+").unwrap(),
 5436            ],
 5437            ..LanguageConfig::default()
 5438        },
 5439        None,
 5440    ));
 5441    let rust_language = Arc::new(Language::new(
 5442        LanguageConfig {
 5443            name: "Rust".into(),
 5444            line_comments: vec!["// ".into(), "/// ".into()],
 5445            ..LanguageConfig::default()
 5446        },
 5447        Some(tree_sitter_rust::LANGUAGE.into()),
 5448    ));
 5449
 5450    let plaintext_language = Arc::new(Language::new(
 5451        LanguageConfig {
 5452            name: "Plain Text".into(),
 5453            ..LanguageConfig::default()
 5454        },
 5455        None,
 5456    ));
 5457
 5458    // Test basic rewrapping of a long line with a cursor
 5459    assert_rewrap(
 5460        indoc! {"
 5461            // ˇThis is a long comment that needs to be wrapped.
 5462        "},
 5463        indoc! {"
 5464            // ˇThis is a long comment that needs to
 5465            // be wrapped.
 5466        "},
 5467        cpp_language.clone(),
 5468        &mut cx,
 5469    );
 5470
 5471    // Test rewrapping a full selection
 5472    assert_rewrap(
 5473        indoc! {"
 5474            «// This selected long comment needs to be wrapped.ˇ»"
 5475        },
 5476        indoc! {"
 5477            «// This selected long comment needs to
 5478            // be wrapped.ˇ»"
 5479        },
 5480        cpp_language.clone(),
 5481        &mut cx,
 5482    );
 5483
 5484    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5485    assert_rewrap(
 5486        indoc! {"
 5487            // ˇThis is the first line.
 5488            // Thisˇ is the second line.
 5489            // This is the thirdˇ line, all part of one paragraph.
 5490         "},
 5491        indoc! {"
 5492            // ˇThis is the first line. Thisˇ is the
 5493            // second line. This is the thirdˇ line,
 5494            // all part of one paragraph.
 5495         "},
 5496        cpp_language.clone(),
 5497        &mut cx,
 5498    );
 5499
 5500    // Test multiple cursors in different paragraphs trigger separate rewraps
 5501    assert_rewrap(
 5502        indoc! {"
 5503            // ˇThis is the first paragraph, first line.
 5504            // ˇThis is the first paragraph, second line.
 5505
 5506            // ˇThis is the second paragraph, first line.
 5507            // ˇThis is the second paragraph, second line.
 5508        "},
 5509        indoc! {"
 5510            // ˇThis is the first paragraph, first
 5511            // line. ˇThis is the first paragraph,
 5512            // second line.
 5513
 5514            // ˇThis is the second paragraph, first
 5515            // line. ˇThis is the second paragraph,
 5516            // second line.
 5517        "},
 5518        cpp_language.clone(),
 5519        &mut cx,
 5520    );
 5521
 5522    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5523    assert_rewrap(
 5524        indoc! {"
 5525            «// A regular long long comment to be wrapped.
 5526            /// A documentation long comment to be wrapped.ˇ»
 5527          "},
 5528        indoc! {"
 5529            «// A regular long long comment to be
 5530            // wrapped.
 5531            /// A documentation long comment to be
 5532            /// wrapped.ˇ»
 5533          "},
 5534        rust_language.clone(),
 5535        &mut cx,
 5536    );
 5537
 5538    // Test that change in indentation level trigger seperate rewraps
 5539    assert_rewrap(
 5540        indoc! {"
 5541            fn foo() {
 5542                «// This is a long comment at the base indent.
 5543                    // This is a long comment at the next indent.ˇ»
 5544            }
 5545        "},
 5546        indoc! {"
 5547            fn foo() {
 5548                «// This is a long comment at the
 5549                // base indent.
 5550                    // This is a long comment at the
 5551                    // next indent.ˇ»
 5552            }
 5553        "},
 5554        rust_language.clone(),
 5555        &mut cx,
 5556    );
 5557
 5558    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5559    assert_rewrap(
 5560        indoc! {"
 5561            # ˇThis is a long comment using a pound sign.
 5562        "},
 5563        indoc! {"
 5564            # ˇThis is a long comment using a pound
 5565            # sign.
 5566        "},
 5567        python_language,
 5568        &mut cx,
 5569    );
 5570
 5571    // Test rewrapping only affects comments, not code even when selected
 5572    assert_rewrap(
 5573        indoc! {"
 5574            «/// This doc comment is long and should be wrapped.
 5575            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5576        "},
 5577        indoc! {"
 5578            «/// This doc comment is long and should
 5579            /// be wrapped.
 5580            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5581        "},
 5582        rust_language.clone(),
 5583        &mut cx,
 5584    );
 5585
 5586    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5587    assert_rewrap(
 5588        indoc! {"
 5589            # Header
 5590
 5591            A long long long line of markdown text to wrap.ˇ
 5592         "},
 5593        indoc! {"
 5594            # Header
 5595
 5596            A long long long line of markdown text
 5597            to wrap.ˇ
 5598         "},
 5599        markdown_language.clone(),
 5600        &mut cx,
 5601    );
 5602
 5603    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5604    assert_rewrap(
 5605        indoc! {"
 5606            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5607            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5608            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5609        "},
 5610        indoc! {"
 5611            «1. This is a numbered list item that is
 5612               very long and needs to be wrapped
 5613               properly.
 5614            2. This is a numbered list item that is
 5615               very long and needs to be wrapped
 5616               properly.
 5617            - This is an unordered list item that is
 5618              also very long and should not merge
 5619              with the numbered item.ˇ»
 5620        "},
 5621        markdown_language.clone(),
 5622        &mut cx,
 5623    );
 5624
 5625    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5626    assert_rewrap(
 5627        indoc! {"
 5628            «1. This is a numbered list item that is
 5629            very long and needs to be wrapped
 5630            properly.
 5631            2. This is a numbered list item that is
 5632            very long and needs to be wrapped
 5633            properly.
 5634            - This is an unordered list item that is
 5635            also very long and should not merge with
 5636            the numbered item.ˇ»
 5637        "},
 5638        indoc! {"
 5639            «1. This is a numbered list item that is
 5640               very long and needs to be wrapped
 5641               properly.
 5642            2. This is a numbered list item that is
 5643               very long and needs to be wrapped
 5644               properly.
 5645            - This is an unordered list item that is
 5646              also very long and should not merge
 5647              with the numbered item.ˇ»
 5648        "},
 5649        markdown_language.clone(),
 5650        &mut cx,
 5651    );
 5652
 5653    // Test that rewrapping maintain indents even when they already exists.
 5654    assert_rewrap(
 5655        indoc! {"
 5656            «1. This is a numbered list
 5657               item that is very long and needs to be wrapped properly.
 5658            2. This is a numbered list
 5659               item that is very long and needs to be wrapped properly.
 5660            - This is an unordered list item that is also very long and
 5661              should not merge with the numbered item.ˇ»
 5662        "},
 5663        indoc! {"
 5664            «1. This is a numbered list item that is
 5665               very long and needs to be wrapped
 5666               properly.
 5667            2. This is a numbered list item that is
 5668               very long and needs to be wrapped
 5669               properly.
 5670            - This is an unordered list item that is
 5671              also very long and should not merge
 5672              with the numbered item.ˇ»
 5673        "},
 5674        markdown_language,
 5675        &mut cx,
 5676    );
 5677
 5678    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5679    assert_rewrap(
 5680        indoc! {"
 5681            ˇThis is a very long line of plain text that will be wrapped.
 5682        "},
 5683        indoc! {"
 5684            ˇThis is a very long line of plain text
 5685            that will be wrapped.
 5686        "},
 5687        plaintext_language.clone(),
 5688        &mut cx,
 5689    );
 5690
 5691    // Test that non-commented code acts as a paragraph boundary within a selection
 5692    assert_rewrap(
 5693        indoc! {"
 5694               «// This is the first long comment block to be wrapped.
 5695               fn my_func(a: u32);
 5696               // This is the second long comment block to be wrapped.ˇ»
 5697           "},
 5698        indoc! {"
 5699               «// This is the first long comment block
 5700               // to be wrapped.
 5701               fn my_func(a: u32);
 5702               // This is the second long comment block
 5703               // to be wrapped.ˇ»
 5704           "},
 5705        rust_language,
 5706        &mut cx,
 5707    );
 5708
 5709    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5710    assert_rewrap(
 5711        indoc! {"
 5712            «ˇThis is a very long line that will be wrapped.
 5713
 5714            This is another paragraph in the same selection.»
 5715
 5716            «\tThis is a very long indented line that will be wrapped.ˇ»
 5717         "},
 5718        indoc! {"
 5719            «ˇThis is a very long line that will be
 5720            wrapped.
 5721
 5722            This is another paragraph in the same
 5723            selection.»
 5724
 5725            «\tThis is a very long indented line
 5726            \tthat will be wrapped.ˇ»
 5727         "},
 5728        plaintext_language,
 5729        &mut cx,
 5730    );
 5731
 5732    // Test that an empty comment line acts as a paragraph boundary
 5733    assert_rewrap(
 5734        indoc! {"
 5735            // ˇThis is a long comment that will be wrapped.
 5736            //
 5737            // And this is another long comment that will also be wrapped.ˇ
 5738         "},
 5739        indoc! {"
 5740            // ˇThis is a long comment that will be
 5741            // wrapped.
 5742            //
 5743            // And this is another long comment that
 5744            // will also be wrapped.ˇ
 5745         "},
 5746        cpp_language,
 5747        &mut cx,
 5748    );
 5749
 5750    #[track_caller]
 5751    fn assert_rewrap(
 5752        unwrapped_text: &str,
 5753        wrapped_text: &str,
 5754        language: Arc<Language>,
 5755        cx: &mut EditorTestContext,
 5756    ) {
 5757        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5758        cx.set_state(unwrapped_text);
 5759        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5760        cx.assert_editor_state(wrapped_text);
 5761    }
 5762}
 5763
 5764#[gpui::test]
 5765async fn test_hard_wrap(cx: &mut TestAppContext) {
 5766    init_test(cx, |_| {});
 5767    let mut cx = EditorTestContext::new(cx).await;
 5768
 5769    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5770    cx.update_editor(|editor, _, cx| {
 5771        editor.set_hard_wrap(Some(14), cx);
 5772    });
 5773
 5774    cx.set_state(indoc!(
 5775        "
 5776        one two three ˇ
 5777        "
 5778    ));
 5779    cx.simulate_input("four");
 5780    cx.run_until_parked();
 5781
 5782    cx.assert_editor_state(indoc!(
 5783        "
 5784        one two three
 5785        fourˇ
 5786        "
 5787    ));
 5788
 5789    cx.update_editor(|editor, window, cx| {
 5790        editor.newline(&Default::default(), window, cx);
 5791    });
 5792    cx.run_until_parked();
 5793    cx.assert_editor_state(indoc!(
 5794        "
 5795        one two three
 5796        four
 5797        ˇ
 5798        "
 5799    ));
 5800
 5801    cx.simulate_input("five");
 5802    cx.run_until_parked();
 5803    cx.assert_editor_state(indoc!(
 5804        "
 5805        one two three
 5806        four
 5807        fiveˇ
 5808        "
 5809    ));
 5810
 5811    cx.update_editor(|editor, window, cx| {
 5812        editor.newline(&Default::default(), window, cx);
 5813    });
 5814    cx.run_until_parked();
 5815    cx.simulate_input("# ");
 5816    cx.run_until_parked();
 5817    cx.assert_editor_state(indoc!(
 5818        "
 5819        one two three
 5820        four
 5821        five
 5822        # ˇ
 5823        "
 5824    ));
 5825
 5826    cx.update_editor(|editor, window, cx| {
 5827        editor.newline(&Default::default(), window, cx);
 5828    });
 5829    cx.run_until_parked();
 5830    cx.assert_editor_state(indoc!(
 5831        "
 5832        one two three
 5833        four
 5834        five
 5835        #\x20
 5836 5837        "
 5838    ));
 5839
 5840    cx.simulate_input(" 6");
 5841    cx.run_until_parked();
 5842    cx.assert_editor_state(indoc!(
 5843        "
 5844        one two three
 5845        four
 5846        five
 5847        #
 5848        # 6ˇ
 5849        "
 5850    ));
 5851}
 5852
 5853#[gpui::test]
 5854async fn test_clipboard(cx: &mut TestAppContext) {
 5855    init_test(cx, |_| {});
 5856
 5857    let mut cx = EditorTestContext::new(cx).await;
 5858
 5859    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5860    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5861    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5862
 5863    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5864    cx.set_state("two ˇfour ˇsix ˇ");
 5865    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5866    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5867
 5868    // Paste again but with only two cursors. Since the number of cursors doesn't
 5869    // match the number of slices in the clipboard, the entire clipboard text
 5870    // is pasted at each cursor.
 5871    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5872    cx.update_editor(|e, window, cx| {
 5873        e.handle_input("( ", window, cx);
 5874        e.paste(&Paste, window, cx);
 5875        e.handle_input(") ", window, cx);
 5876    });
 5877    cx.assert_editor_state(
 5878        &([
 5879            "( one✅ ",
 5880            "three ",
 5881            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5882            "three ",
 5883            "five ) ˇ",
 5884        ]
 5885        .join("\n")),
 5886    );
 5887
 5888    // Cut with three selections, one of which is full-line.
 5889    cx.set_state(indoc! {"
 5890        1«2ˇ»3
 5891        4ˇ567
 5892        «8ˇ»9"});
 5893    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5894    cx.assert_editor_state(indoc! {"
 5895        1ˇ3
 5896        ˇ9"});
 5897
 5898    // Paste with three selections, noticing how the copied selection that was full-line
 5899    // gets inserted before the second cursor.
 5900    cx.set_state(indoc! {"
 5901        1ˇ3
 5902 5903        «oˇ»ne"});
 5904    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5905    cx.assert_editor_state(indoc! {"
 5906        12ˇ3
 5907        4567
 5908 5909        8ˇne"});
 5910
 5911    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5912    cx.set_state(indoc! {"
 5913        The quick brown
 5914        fox juˇmps over
 5915        the lazy dog"});
 5916    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5917    assert_eq!(
 5918        cx.read_from_clipboard()
 5919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5920        Some("fox jumps over\n".to_string())
 5921    );
 5922
 5923    // Paste with three selections, noticing how the copied full-line selection is inserted
 5924    // before the empty selections but replaces the selection that is non-empty.
 5925    cx.set_state(indoc! {"
 5926        Tˇhe quick brown
 5927        «foˇ»x jumps over
 5928        tˇhe lazy dog"});
 5929    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5930    cx.assert_editor_state(indoc! {"
 5931        fox jumps over
 5932        Tˇhe quick brown
 5933        fox jumps over
 5934        ˇx jumps over
 5935        fox jumps over
 5936        tˇhe lazy dog"});
 5937}
 5938
 5939#[gpui::test]
 5940async fn test_copy_trim(cx: &mut TestAppContext) {
 5941    init_test(cx, |_| {});
 5942
 5943    let mut cx = EditorTestContext::new(cx).await;
 5944    cx.set_state(
 5945        r#"            «for selection in selections.iter() {
 5946            let mut start = selection.start;
 5947            let mut end = selection.end;
 5948            let is_entire_line = selection.is_empty();
 5949            if is_entire_line {
 5950                start = Point::new(start.row, 0);ˇ»
 5951                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5952            }
 5953        "#,
 5954    );
 5955    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5956    assert_eq!(
 5957        cx.read_from_clipboard()
 5958            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5959        Some(
 5960            "for selection in selections.iter() {
 5961            let mut start = selection.start;
 5962            let mut end = selection.end;
 5963            let is_entire_line = selection.is_empty();
 5964            if is_entire_line {
 5965                start = Point::new(start.row, 0);"
 5966                .to_string()
 5967        ),
 5968        "Regular copying preserves all indentation selected",
 5969    );
 5970    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5971    assert_eq!(
 5972        cx.read_from_clipboard()
 5973            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5974        Some(
 5975            "for selection in selections.iter() {
 5976let mut start = selection.start;
 5977let mut end = selection.end;
 5978let is_entire_line = selection.is_empty();
 5979if is_entire_line {
 5980    start = Point::new(start.row, 0);"
 5981                .to_string()
 5982        ),
 5983        "Copying with stripping should strip all leading whitespaces"
 5984    );
 5985
 5986    cx.set_state(
 5987        r#"       «     for selection in selections.iter() {
 5988            let mut start = selection.start;
 5989            let mut end = selection.end;
 5990            let is_entire_line = selection.is_empty();
 5991            if is_entire_line {
 5992                start = Point::new(start.row, 0);ˇ»
 5993                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5994            }
 5995        "#,
 5996    );
 5997    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5998    assert_eq!(
 5999        cx.read_from_clipboard()
 6000            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6001        Some(
 6002            "     for selection in selections.iter() {
 6003            let mut start = selection.start;
 6004            let mut end = selection.end;
 6005            let is_entire_line = selection.is_empty();
 6006            if is_entire_line {
 6007                start = Point::new(start.row, 0);"
 6008                .to_string()
 6009        ),
 6010        "Regular copying preserves all indentation selected",
 6011    );
 6012    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6013    assert_eq!(
 6014        cx.read_from_clipboard()
 6015            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6016        Some(
 6017            "for selection in selections.iter() {
 6018let mut start = selection.start;
 6019let mut end = selection.end;
 6020let is_entire_line = selection.is_empty();
 6021if is_entire_line {
 6022    start = Point::new(start.row, 0);"
 6023                .to_string()
 6024        ),
 6025        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6026    );
 6027
 6028    cx.set_state(
 6029        r#"       «ˇ     for selection in selections.iter() {
 6030            let mut start = selection.start;
 6031            let mut end = selection.end;
 6032            let is_entire_line = selection.is_empty();
 6033            if is_entire_line {
 6034                start = Point::new(start.row, 0);»
 6035                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6036            }
 6037        "#,
 6038    );
 6039    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6040    assert_eq!(
 6041        cx.read_from_clipboard()
 6042            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6043        Some(
 6044            "     for selection in selections.iter() {
 6045            let mut start = selection.start;
 6046            let mut end = selection.end;
 6047            let is_entire_line = selection.is_empty();
 6048            if is_entire_line {
 6049                start = Point::new(start.row, 0);"
 6050                .to_string()
 6051        ),
 6052        "Regular copying for reverse selection works the same",
 6053    );
 6054    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6055    assert_eq!(
 6056        cx.read_from_clipboard()
 6057            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6058        Some(
 6059            "for selection in selections.iter() {
 6060let mut start = selection.start;
 6061let mut end = selection.end;
 6062let is_entire_line = selection.is_empty();
 6063if is_entire_line {
 6064    start = Point::new(start.row, 0);"
 6065                .to_string()
 6066        ),
 6067        "Copying with stripping for reverse selection works the same"
 6068    );
 6069
 6070    cx.set_state(
 6071        r#"            for selection «in selections.iter() {
 6072            let mut start = selection.start;
 6073            let mut end = selection.end;
 6074            let is_entire_line = selection.is_empty();
 6075            if is_entire_line {
 6076                start = Point::new(start.row, 0);ˇ»
 6077                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6078            }
 6079        "#,
 6080    );
 6081    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6082    assert_eq!(
 6083        cx.read_from_clipboard()
 6084            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6085        Some(
 6086            "in selections.iter() {
 6087            let mut start = selection.start;
 6088            let mut end = selection.end;
 6089            let is_entire_line = selection.is_empty();
 6090            if is_entire_line {
 6091                start = Point::new(start.row, 0);"
 6092                .to_string()
 6093        ),
 6094        "When selecting past the indent, the copying works as usual",
 6095    );
 6096    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6097    assert_eq!(
 6098        cx.read_from_clipboard()
 6099            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6100        Some(
 6101            "in selections.iter() {
 6102            let mut start = selection.start;
 6103            let mut end = selection.end;
 6104            let is_entire_line = selection.is_empty();
 6105            if is_entire_line {
 6106                start = Point::new(start.row, 0);"
 6107                .to_string()
 6108        ),
 6109        "When selecting past the indent, nothing is trimmed"
 6110    );
 6111
 6112    cx.set_state(
 6113        r#"            «for selection in selections.iter() {
 6114            let mut start = selection.start;
 6115
 6116            let mut end = selection.end;
 6117            let is_entire_line = selection.is_empty();
 6118            if is_entire_line {
 6119                start = Point::new(start.row, 0);
 6120ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6121            }
 6122        "#,
 6123    );
 6124    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6125    assert_eq!(
 6126        cx.read_from_clipboard()
 6127            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6128        Some(
 6129            "for selection in selections.iter() {
 6130let mut start = selection.start;
 6131
 6132let mut end = selection.end;
 6133let is_entire_line = selection.is_empty();
 6134if is_entire_line {
 6135    start = Point::new(start.row, 0);
 6136"
 6137            .to_string()
 6138        ),
 6139        "Copying with stripping should ignore empty lines"
 6140    );
 6141}
 6142
 6143#[gpui::test]
 6144async fn test_paste_multiline(cx: &mut TestAppContext) {
 6145    init_test(cx, |_| {});
 6146
 6147    let mut cx = EditorTestContext::new(cx).await;
 6148    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6149
 6150    // Cut an indented block, without the leading whitespace.
 6151    cx.set_state(indoc! {"
 6152        const a: B = (
 6153            c(),
 6154            «d(
 6155                e,
 6156                f
 6157            )ˇ»
 6158        );
 6159    "});
 6160    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6161    cx.assert_editor_state(indoc! {"
 6162        const a: B = (
 6163            c(),
 6164            ˇ
 6165        );
 6166    "});
 6167
 6168    // Paste it at the same position.
 6169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6170    cx.assert_editor_state(indoc! {"
 6171        const a: B = (
 6172            c(),
 6173            d(
 6174                e,
 6175                f
 6176 6177        );
 6178    "});
 6179
 6180    // Paste it at a line with a lower indent level.
 6181    cx.set_state(indoc! {"
 6182        ˇ
 6183        const a: B = (
 6184            c(),
 6185        );
 6186    "});
 6187    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6188    cx.assert_editor_state(indoc! {"
 6189        d(
 6190            e,
 6191            f
 6192 6193        const a: B = (
 6194            c(),
 6195        );
 6196    "});
 6197
 6198    // Cut an indented block, with the leading whitespace.
 6199    cx.set_state(indoc! {"
 6200        const a: B = (
 6201            c(),
 6202        «    d(
 6203                e,
 6204                f
 6205            )
 6206        ˇ»);
 6207    "});
 6208    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6209    cx.assert_editor_state(indoc! {"
 6210        const a: B = (
 6211            c(),
 6212        ˇ);
 6213    "});
 6214
 6215    // Paste it at the same position.
 6216    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6217    cx.assert_editor_state(indoc! {"
 6218        const a: B = (
 6219            c(),
 6220            d(
 6221                e,
 6222                f
 6223            )
 6224        ˇ);
 6225    "});
 6226
 6227    // Paste it at a line with a higher indent level.
 6228    cx.set_state(indoc! {"
 6229        const a: B = (
 6230            c(),
 6231            d(
 6232                e,
 6233 6234            )
 6235        );
 6236    "});
 6237    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6238    cx.assert_editor_state(indoc! {"
 6239        const a: B = (
 6240            c(),
 6241            d(
 6242                e,
 6243                f    d(
 6244                    e,
 6245                    f
 6246                )
 6247        ˇ
 6248            )
 6249        );
 6250    "});
 6251
 6252    // Copy an indented block, starting mid-line
 6253    cx.set_state(indoc! {"
 6254        const a: B = (
 6255            c(),
 6256            somethin«g(
 6257                e,
 6258                f
 6259            )ˇ»
 6260        );
 6261    "});
 6262    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6263
 6264    // Paste it on a line with a lower indent level
 6265    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6266    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6267    cx.assert_editor_state(indoc! {"
 6268        const a: B = (
 6269            c(),
 6270            something(
 6271                e,
 6272                f
 6273            )
 6274        );
 6275        g(
 6276            e,
 6277            f
 6278"});
 6279}
 6280
 6281#[gpui::test]
 6282async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6283    init_test(cx, |_| {});
 6284
 6285    cx.write_to_clipboard(ClipboardItem::new_string(
 6286        "    d(\n        e\n    );\n".into(),
 6287    ));
 6288
 6289    let mut cx = EditorTestContext::new(cx).await;
 6290    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6291
 6292    cx.set_state(indoc! {"
 6293        fn a() {
 6294            b();
 6295            if c() {
 6296                ˇ
 6297            }
 6298        }
 6299    "});
 6300
 6301    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6302    cx.assert_editor_state(indoc! {"
 6303        fn a() {
 6304            b();
 6305            if c() {
 6306                d(
 6307                    e
 6308                );
 6309        ˇ
 6310            }
 6311        }
 6312    "});
 6313
 6314    cx.set_state(indoc! {"
 6315        fn a() {
 6316            b();
 6317            ˇ
 6318        }
 6319    "});
 6320
 6321    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6322    cx.assert_editor_state(indoc! {"
 6323        fn a() {
 6324            b();
 6325            d(
 6326                e
 6327            );
 6328        ˇ
 6329        }
 6330    "});
 6331}
 6332
 6333#[gpui::test]
 6334fn test_select_all(cx: &mut TestAppContext) {
 6335    init_test(cx, |_| {});
 6336
 6337    let editor = cx.add_window(|window, cx| {
 6338        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6339        build_editor(buffer, window, cx)
 6340    });
 6341    _ = editor.update(cx, |editor, window, cx| {
 6342        editor.select_all(&SelectAll, window, cx);
 6343        assert_eq!(
 6344            editor.selections.display_ranges(cx),
 6345            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6346        );
 6347    });
 6348}
 6349
 6350#[gpui::test]
 6351fn test_select_line(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353
 6354    let editor = cx.add_window(|window, cx| {
 6355        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6356        build_editor(buffer, window, cx)
 6357    });
 6358    _ = editor.update(cx, |editor, window, cx| {
 6359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6360            s.select_display_ranges([
 6361                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6362                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6363                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6364                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6365            ])
 6366        });
 6367        editor.select_line(&SelectLine, window, cx);
 6368        assert_eq!(
 6369            editor.selections.display_ranges(cx),
 6370            vec![
 6371                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6372                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6373            ]
 6374        );
 6375    });
 6376
 6377    _ = editor.update(cx, |editor, window, cx| {
 6378        editor.select_line(&SelectLine, window, cx);
 6379        assert_eq!(
 6380            editor.selections.display_ranges(cx),
 6381            vec![
 6382                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6383                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6384            ]
 6385        );
 6386    });
 6387
 6388    _ = editor.update(cx, |editor, window, cx| {
 6389        editor.select_line(&SelectLine, window, cx);
 6390        assert_eq!(
 6391            editor.selections.display_ranges(cx),
 6392            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6393        );
 6394    });
 6395}
 6396
 6397#[gpui::test]
 6398async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6399    init_test(cx, |_| {});
 6400    let mut cx = EditorTestContext::new(cx).await;
 6401
 6402    #[track_caller]
 6403    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6404        cx.set_state(initial_state);
 6405        cx.update_editor(|e, window, cx| {
 6406            e.split_selection_into_lines(&Default::default(), window, cx)
 6407        });
 6408        cx.assert_editor_state(expected_state);
 6409    }
 6410
 6411    // Selection starts and ends at the middle of lines, left-to-right
 6412    test(
 6413        &mut cx,
 6414        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6415        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6416    );
 6417    // Same thing, right-to-left
 6418    test(
 6419        &mut cx,
 6420        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6421        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6422    );
 6423
 6424    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6425    test(
 6426        &mut cx,
 6427        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6428        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6429    );
 6430    // Same thing, right-to-left
 6431    test(
 6432        &mut cx,
 6433        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6434        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6435    );
 6436
 6437    // Whole buffer, left-to-right, last line ends with newline
 6438    test(
 6439        &mut cx,
 6440        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6441        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6442    );
 6443    // Same thing, right-to-left
 6444    test(
 6445        &mut cx,
 6446        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6447        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6448    );
 6449
 6450    // Starts at the end of a line, ends at the start of another
 6451    test(
 6452        &mut cx,
 6453        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6454        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6455    );
 6456}
 6457
 6458#[gpui::test]
 6459async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6460    init_test(cx, |_| {});
 6461
 6462    let editor = cx.add_window(|window, cx| {
 6463        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6464        build_editor(buffer, window, cx)
 6465    });
 6466
 6467    // setup
 6468    _ = editor.update(cx, |editor, window, cx| {
 6469        editor.fold_creases(
 6470            vec![
 6471                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6472                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6473                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6474            ],
 6475            true,
 6476            window,
 6477            cx,
 6478        );
 6479        assert_eq!(
 6480            editor.display_text(cx),
 6481            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6482        );
 6483    });
 6484
 6485    _ = editor.update(cx, |editor, window, cx| {
 6486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6487            s.select_display_ranges([
 6488                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6489                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6490                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6491                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6492            ])
 6493        });
 6494        editor.split_selection_into_lines(&Default::default(), window, cx);
 6495        assert_eq!(
 6496            editor.display_text(cx),
 6497            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6498        );
 6499    });
 6500    EditorTestContext::for_editor(editor, cx)
 6501        .await
 6502        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6503
 6504    _ = editor.update(cx, |editor, window, cx| {
 6505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6506            s.select_display_ranges([
 6507                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6508            ])
 6509        });
 6510        editor.split_selection_into_lines(&Default::default(), window, cx);
 6511        assert_eq!(
 6512            editor.display_text(cx),
 6513            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6514        );
 6515        assert_eq!(
 6516            editor.selections.display_ranges(cx),
 6517            [
 6518                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6519                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6520                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6521                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6522                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6523                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6524                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6525            ]
 6526        );
 6527    });
 6528    EditorTestContext::for_editor(editor, cx)
 6529        .await
 6530        .assert_editor_state(
 6531            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6532        );
 6533}
 6534
 6535#[gpui::test]
 6536async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6537    init_test(cx, |_| {});
 6538
 6539    let mut cx = EditorTestContext::new(cx).await;
 6540
 6541    cx.set_state(indoc!(
 6542        r#"abc
 6543           defˇghi
 6544
 6545           jk
 6546           nlmo
 6547           "#
 6548    ));
 6549
 6550    cx.update_editor(|editor, window, cx| {
 6551        editor.add_selection_above(&Default::default(), window, cx);
 6552    });
 6553
 6554    cx.assert_editor_state(indoc!(
 6555        r#"abcˇ
 6556           defˇghi
 6557
 6558           jk
 6559           nlmo
 6560           "#
 6561    ));
 6562
 6563    cx.update_editor(|editor, window, cx| {
 6564        editor.add_selection_above(&Default::default(), window, cx);
 6565    });
 6566
 6567    cx.assert_editor_state(indoc!(
 6568        r#"abcˇ
 6569            defˇghi
 6570
 6571            jk
 6572            nlmo
 6573            "#
 6574    ));
 6575
 6576    cx.update_editor(|editor, window, cx| {
 6577        editor.add_selection_below(&Default::default(), window, cx);
 6578    });
 6579
 6580    cx.assert_editor_state(indoc!(
 6581        r#"abc
 6582           defˇghi
 6583
 6584           jk
 6585           nlmo
 6586           "#
 6587    ));
 6588
 6589    cx.update_editor(|editor, window, cx| {
 6590        editor.undo_selection(&Default::default(), window, cx);
 6591    });
 6592
 6593    cx.assert_editor_state(indoc!(
 6594        r#"abcˇ
 6595           defˇghi
 6596
 6597           jk
 6598           nlmo
 6599           "#
 6600    ));
 6601
 6602    cx.update_editor(|editor, window, cx| {
 6603        editor.redo_selection(&Default::default(), window, cx);
 6604    });
 6605
 6606    cx.assert_editor_state(indoc!(
 6607        r#"abc
 6608           defˇghi
 6609
 6610           jk
 6611           nlmo
 6612           "#
 6613    ));
 6614
 6615    cx.update_editor(|editor, window, cx| {
 6616        editor.add_selection_below(&Default::default(), window, cx);
 6617    });
 6618
 6619    cx.assert_editor_state(indoc!(
 6620        r#"abc
 6621           defˇghi
 6622           ˇ
 6623           jk
 6624           nlmo
 6625           "#
 6626    ));
 6627
 6628    cx.update_editor(|editor, window, cx| {
 6629        editor.add_selection_below(&Default::default(), window, cx);
 6630    });
 6631
 6632    cx.assert_editor_state(indoc!(
 6633        r#"abc
 6634           defˇghi
 6635           ˇ
 6636           jkˇ
 6637           nlmo
 6638           "#
 6639    ));
 6640
 6641    cx.update_editor(|editor, window, cx| {
 6642        editor.add_selection_below(&Default::default(), window, cx);
 6643    });
 6644
 6645    cx.assert_editor_state(indoc!(
 6646        r#"abc
 6647           defˇghi
 6648           ˇ
 6649           jkˇ
 6650           nlmˇo
 6651           "#
 6652    ));
 6653
 6654    cx.update_editor(|editor, window, cx| {
 6655        editor.add_selection_below(&Default::default(), window, cx);
 6656    });
 6657
 6658    cx.assert_editor_state(indoc!(
 6659        r#"abc
 6660           defˇghi
 6661           ˇ
 6662           jkˇ
 6663           nlmˇo
 6664           ˇ"#
 6665    ));
 6666
 6667    // change selections
 6668    cx.set_state(indoc!(
 6669        r#"abc
 6670           def«ˇg»hi
 6671
 6672           jk
 6673           nlmo
 6674           "#
 6675    ));
 6676
 6677    cx.update_editor(|editor, window, cx| {
 6678        editor.add_selection_below(&Default::default(), window, cx);
 6679    });
 6680
 6681    cx.assert_editor_state(indoc!(
 6682        r#"abc
 6683           def«ˇg»hi
 6684
 6685           jk
 6686           nlm«ˇo»
 6687           "#
 6688    ));
 6689
 6690    cx.update_editor(|editor, window, cx| {
 6691        editor.add_selection_below(&Default::default(), window, cx);
 6692    });
 6693
 6694    cx.assert_editor_state(indoc!(
 6695        r#"abc
 6696           def«ˇg»hi
 6697
 6698           jk
 6699           nlm«ˇo»
 6700           "#
 6701    ));
 6702
 6703    cx.update_editor(|editor, window, cx| {
 6704        editor.add_selection_above(&Default::default(), window, cx);
 6705    });
 6706
 6707    cx.assert_editor_state(indoc!(
 6708        r#"abc
 6709           def«ˇg»hi
 6710
 6711           jk
 6712           nlmo
 6713           "#
 6714    ));
 6715
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_above(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"abc
 6722           def«ˇg»hi
 6723
 6724           jk
 6725           nlmo
 6726           "#
 6727    ));
 6728
 6729    // Change selections again
 6730    cx.set_state(indoc!(
 6731        r#"a«bc
 6732           defgˇ»hi
 6733
 6734           jk
 6735           nlmo
 6736           "#
 6737    ));
 6738
 6739    cx.update_editor(|editor, window, cx| {
 6740        editor.add_selection_below(&Default::default(), window, cx);
 6741    });
 6742
 6743    cx.assert_editor_state(indoc!(
 6744        r#"a«bcˇ»
 6745           d«efgˇ»hi
 6746
 6747           j«kˇ»
 6748           nlmo
 6749           "#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.add_selection_below(&Default::default(), window, cx);
 6754    });
 6755    cx.assert_editor_state(indoc!(
 6756        r#"a«bcˇ»
 6757           d«efgˇ»hi
 6758
 6759           j«kˇ»
 6760           n«lmoˇ»
 6761           "#
 6762    ));
 6763    cx.update_editor(|editor, window, cx| {
 6764        editor.add_selection_above(&Default::default(), window, cx);
 6765    });
 6766
 6767    cx.assert_editor_state(indoc!(
 6768        r#"a«bcˇ»
 6769           d«efgˇ»hi
 6770
 6771           j«kˇ»
 6772           nlmo
 6773           "#
 6774    ));
 6775
 6776    // Change selections again
 6777    cx.set_state(indoc!(
 6778        r#"abc
 6779           d«ˇefghi
 6780
 6781           jk
 6782           nlm»o
 6783           "#
 6784    ));
 6785
 6786    cx.update_editor(|editor, window, cx| {
 6787        editor.add_selection_above(&Default::default(), window, cx);
 6788    });
 6789
 6790    cx.assert_editor_state(indoc!(
 6791        r#"a«ˇbc»
 6792           d«ˇef»ghi
 6793
 6794           j«ˇk»
 6795           n«ˇlm»o
 6796           "#
 6797    ));
 6798
 6799    cx.update_editor(|editor, window, cx| {
 6800        editor.add_selection_below(&Default::default(), window, cx);
 6801    });
 6802
 6803    cx.assert_editor_state(indoc!(
 6804        r#"abc
 6805           d«ˇef»ghi
 6806
 6807           j«ˇk»
 6808           n«ˇlm»o
 6809           "#
 6810    ));
 6811}
 6812
 6813#[gpui::test]
 6814async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6815    init_test(cx, |_| {});
 6816    let mut cx = EditorTestContext::new(cx).await;
 6817
 6818    cx.set_state(indoc!(
 6819        r#"line onˇe
 6820           liˇne two
 6821           line three
 6822           line four"#
 6823    ));
 6824
 6825    cx.update_editor(|editor, window, cx| {
 6826        editor.add_selection_below(&Default::default(), window, cx);
 6827    });
 6828
 6829    // test multiple cursors expand in the same direction
 6830    cx.assert_editor_state(indoc!(
 6831        r#"line onˇe
 6832           liˇne twˇo
 6833           liˇne three
 6834           line four"#
 6835    ));
 6836
 6837    cx.update_editor(|editor, window, cx| {
 6838        editor.add_selection_below(&Default::default(), window, cx);
 6839    });
 6840
 6841    cx.update_editor(|editor, window, cx| {
 6842        editor.add_selection_below(&Default::default(), window, cx);
 6843    });
 6844
 6845    // test multiple cursors expand below overflow
 6846    cx.assert_editor_state(indoc!(
 6847        r#"line onˇe
 6848           liˇne twˇo
 6849           liˇne thˇree
 6850           liˇne foˇur"#
 6851    ));
 6852
 6853    cx.update_editor(|editor, window, cx| {
 6854        editor.add_selection_above(&Default::default(), window, cx);
 6855    });
 6856
 6857    // test multiple cursors retrieves back correctly
 6858    cx.assert_editor_state(indoc!(
 6859        r#"line onˇe
 6860           liˇne twˇo
 6861           liˇne thˇree
 6862           line four"#
 6863    ));
 6864
 6865    cx.update_editor(|editor, window, cx| {
 6866        editor.add_selection_above(&Default::default(), window, cx);
 6867    });
 6868
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.add_selection_above(&Default::default(), window, cx);
 6871    });
 6872
 6873    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6874    cx.assert_editor_state(indoc!(
 6875        r#"liˇne onˇe
 6876           liˇne two
 6877           line three
 6878           line four"#
 6879    ));
 6880
 6881    cx.update_editor(|editor, window, cx| {
 6882        editor.undo_selection(&Default::default(), window, cx);
 6883    });
 6884
 6885    // test undo
 6886    cx.assert_editor_state(indoc!(
 6887        r#"line onˇe
 6888           liˇne twˇo
 6889           line three
 6890           line four"#
 6891    ));
 6892
 6893    cx.update_editor(|editor, window, cx| {
 6894        editor.redo_selection(&Default::default(), window, cx);
 6895    });
 6896
 6897    // test redo
 6898    cx.assert_editor_state(indoc!(
 6899        r#"liˇne onˇe
 6900           liˇne two
 6901           line three
 6902           line four"#
 6903    ));
 6904
 6905    cx.set_state(indoc!(
 6906        r#"abcd
 6907           ef«ghˇ»
 6908           ijkl
 6909           «mˇ»nop"#
 6910    ));
 6911
 6912    cx.update_editor(|editor, window, cx| {
 6913        editor.add_selection_above(&Default::default(), window, cx);
 6914    });
 6915
 6916    // test multiple selections expand in the same direction
 6917    cx.assert_editor_state(indoc!(
 6918        r#"ab«cdˇ»
 6919           ef«ghˇ»
 6920           «iˇ»jkl
 6921           «mˇ»nop"#
 6922    ));
 6923
 6924    cx.update_editor(|editor, window, cx| {
 6925        editor.add_selection_above(&Default::default(), window, cx);
 6926    });
 6927
 6928    // test multiple selection upward overflow
 6929    cx.assert_editor_state(indoc!(
 6930        r#"ab«cdˇ»
 6931           «eˇ»f«ghˇ»
 6932           «iˇ»jkl
 6933           «mˇ»nop"#
 6934    ));
 6935
 6936    cx.update_editor(|editor, window, cx| {
 6937        editor.add_selection_below(&Default::default(), window, cx);
 6938    });
 6939
 6940    // test multiple selection retrieves back correctly
 6941    cx.assert_editor_state(indoc!(
 6942        r#"abcd
 6943           ef«ghˇ»
 6944           «iˇ»jkl
 6945           «mˇ»nop"#
 6946    ));
 6947
 6948    cx.update_editor(|editor, window, cx| {
 6949        editor.add_selection_below(&Default::default(), window, cx);
 6950    });
 6951
 6952    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6953    cx.assert_editor_state(indoc!(
 6954        r#"abcd
 6955           ef«ghˇ»
 6956           ij«klˇ»
 6957           «mˇ»nop"#
 6958    ));
 6959
 6960    cx.update_editor(|editor, window, cx| {
 6961        editor.undo_selection(&Default::default(), window, cx);
 6962    });
 6963
 6964    // test undo
 6965    cx.assert_editor_state(indoc!(
 6966        r#"abcd
 6967           ef«ghˇ»
 6968           «iˇ»jkl
 6969           «mˇ»nop"#
 6970    ));
 6971
 6972    cx.update_editor(|editor, window, cx| {
 6973        editor.redo_selection(&Default::default(), window, cx);
 6974    });
 6975
 6976    // test redo
 6977    cx.assert_editor_state(indoc!(
 6978        r#"abcd
 6979           ef«ghˇ»
 6980           ij«klˇ»
 6981           «mˇ»nop"#
 6982    ));
 6983}
 6984
 6985#[gpui::test]
 6986async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6987    init_test(cx, |_| {});
 6988    let mut cx = EditorTestContext::new(cx).await;
 6989
 6990    cx.set_state(indoc!(
 6991        r#"line onˇe
 6992           liˇne two
 6993           line three
 6994           line four"#
 6995    ));
 6996
 6997    cx.update_editor(|editor, window, cx| {
 6998        editor.add_selection_below(&Default::default(), window, cx);
 6999        editor.add_selection_below(&Default::default(), window, cx);
 7000        editor.add_selection_below(&Default::default(), window, cx);
 7001    });
 7002
 7003    // initial state with two multi cursor groups
 7004    cx.assert_editor_state(indoc!(
 7005        r#"line onˇe
 7006           liˇne twˇo
 7007           liˇne thˇree
 7008           liˇne foˇur"#
 7009    ));
 7010
 7011    // add single cursor in middle - simulate opt click
 7012    cx.update_editor(|editor, window, cx| {
 7013        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7014        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7015        editor.end_selection(window, cx);
 7016    });
 7017
 7018    cx.assert_editor_state(indoc!(
 7019        r#"line onˇe
 7020           liˇne twˇo
 7021           liˇneˇ thˇree
 7022           liˇne foˇur"#
 7023    ));
 7024
 7025    cx.update_editor(|editor, window, cx| {
 7026        editor.add_selection_above(&Default::default(), window, cx);
 7027    });
 7028
 7029    // test new added selection expands above and existing selection shrinks
 7030    cx.assert_editor_state(indoc!(
 7031        r#"line onˇe
 7032           liˇneˇ twˇo
 7033           liˇneˇ thˇree
 7034           line four"#
 7035    ));
 7036
 7037    cx.update_editor(|editor, window, cx| {
 7038        editor.add_selection_above(&Default::default(), window, cx);
 7039    });
 7040
 7041    // test new added selection expands above and existing selection shrinks
 7042    cx.assert_editor_state(indoc!(
 7043        r#"lineˇ onˇe
 7044           liˇneˇ twˇo
 7045           lineˇ three
 7046           line four"#
 7047    ));
 7048
 7049    // intial state with two selection groups
 7050    cx.set_state(indoc!(
 7051        r#"abcd
 7052           ef«ghˇ»
 7053           ijkl
 7054           «mˇ»nop"#
 7055    ));
 7056
 7057    cx.update_editor(|editor, window, cx| {
 7058        editor.add_selection_above(&Default::default(), window, cx);
 7059        editor.add_selection_above(&Default::default(), window, cx);
 7060    });
 7061
 7062    cx.assert_editor_state(indoc!(
 7063        r#"ab«cdˇ»
 7064           «eˇ»f«ghˇ»
 7065           «iˇ»jkl
 7066           «mˇ»nop"#
 7067    ));
 7068
 7069    // add single selection in middle - simulate opt drag
 7070    cx.update_editor(|editor, window, cx| {
 7071        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7072        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7073        editor.update_selection(
 7074            DisplayPoint::new(DisplayRow(2), 4),
 7075            0,
 7076            gpui::Point::<f32>::default(),
 7077            window,
 7078            cx,
 7079        );
 7080        editor.end_selection(window, cx);
 7081    });
 7082
 7083    cx.assert_editor_state(indoc!(
 7084        r#"ab«cdˇ»
 7085           «eˇ»f«ghˇ»
 7086           «iˇ»jk«lˇ»
 7087           «mˇ»nop"#
 7088    ));
 7089
 7090    cx.update_editor(|editor, window, cx| {
 7091        editor.add_selection_below(&Default::default(), window, cx);
 7092    });
 7093
 7094    // test new added selection expands below, others shrinks from above
 7095    cx.assert_editor_state(indoc!(
 7096        r#"abcd
 7097           ef«ghˇ»
 7098           «iˇ»jk«lˇ»
 7099           «mˇ»no«pˇ»"#
 7100    ));
 7101}
 7102
 7103#[gpui::test]
 7104async fn test_select_next(cx: &mut TestAppContext) {
 7105    init_test(cx, |_| {});
 7106
 7107    let mut cx = EditorTestContext::new(cx).await;
 7108    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7109
 7110    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7111        .unwrap();
 7112    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7113
 7114    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7115        .unwrap();
 7116    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7117
 7118    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7119    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7120
 7121    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7122    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7123
 7124    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7125        .unwrap();
 7126    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7127
 7128    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7129        .unwrap();
 7130    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7131
 7132    // Test selection direction should be preserved
 7133    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7134
 7135    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7136        .unwrap();
 7137    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7138}
 7139
 7140#[gpui::test]
 7141async fn test_select_all_matches(cx: &mut TestAppContext) {
 7142    init_test(cx, |_| {});
 7143
 7144    let mut cx = EditorTestContext::new(cx).await;
 7145
 7146    // Test caret-only selections
 7147    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7148    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7149        .unwrap();
 7150    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7151
 7152    // Test left-to-right selections
 7153    cx.set_state("abc\n«abcˇ»\nabc");
 7154    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7155        .unwrap();
 7156    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7157
 7158    // Test right-to-left selections
 7159    cx.set_state("abc\n«ˇabc»\nabc");
 7160    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7161        .unwrap();
 7162    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7163
 7164    // Test selecting whitespace with caret selection
 7165    cx.set_state("abc\nˇ   abc\nabc");
 7166    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7167        .unwrap();
 7168    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7169
 7170    // Test selecting whitespace with left-to-right selection
 7171    cx.set_state("abc\n«ˇ  »abc\nabc");
 7172    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7173        .unwrap();
 7174    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7175
 7176    // Test no matches with right-to-left selection
 7177    cx.set_state("abc\n«  ˇ»abc\nabc");
 7178    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7179        .unwrap();
 7180    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7181
 7182    // Test with a single word and clip_at_line_ends=true (#29823)
 7183    cx.set_state("aˇbc");
 7184    cx.update_editor(|e, window, cx| {
 7185        e.set_clip_at_line_ends(true, cx);
 7186        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7187        e.set_clip_at_line_ends(false, cx);
 7188    });
 7189    cx.assert_editor_state("«abcˇ»");
 7190}
 7191
 7192#[gpui::test]
 7193async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7194    init_test(cx, |_| {});
 7195
 7196    let mut cx = EditorTestContext::new(cx).await;
 7197
 7198    let large_body_1 = "\nd".repeat(200);
 7199    let large_body_2 = "\ne".repeat(200);
 7200
 7201    cx.set_state(&format!(
 7202        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7203    ));
 7204    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7205        let scroll_position = editor.scroll_position(cx);
 7206        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7207        scroll_position
 7208    });
 7209
 7210    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7211        .unwrap();
 7212    cx.assert_editor_state(&format!(
 7213        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7214    ));
 7215    let scroll_position_after_selection =
 7216        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7217    assert_eq!(
 7218        initial_scroll_position, scroll_position_after_selection,
 7219        "Scroll position should not change after selecting all matches"
 7220    );
 7221}
 7222
 7223#[gpui::test]
 7224async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7225    init_test(cx, |_| {});
 7226
 7227    let mut cx = EditorLspTestContext::new_rust(
 7228        lsp::ServerCapabilities {
 7229            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7230            ..Default::default()
 7231        },
 7232        cx,
 7233    )
 7234    .await;
 7235
 7236    cx.set_state(indoc! {"
 7237        line 1
 7238        line 2
 7239        linˇe 3
 7240        line 4
 7241        line 5
 7242    "});
 7243
 7244    // Make an edit
 7245    cx.update_editor(|editor, window, cx| {
 7246        editor.handle_input("X", window, cx);
 7247    });
 7248
 7249    // Move cursor to a different position
 7250    cx.update_editor(|editor, window, cx| {
 7251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7252            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7253        });
 7254    });
 7255
 7256    cx.assert_editor_state(indoc! {"
 7257        line 1
 7258        line 2
 7259        linXe 3
 7260        line 4
 7261        liˇne 5
 7262    "});
 7263
 7264    cx.lsp
 7265        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7266            Ok(Some(vec![lsp::TextEdit::new(
 7267                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7268                "PREFIX ".to_string(),
 7269            )]))
 7270        });
 7271
 7272    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7273        .unwrap()
 7274        .await
 7275        .unwrap();
 7276
 7277    cx.assert_editor_state(indoc! {"
 7278        PREFIX line 1
 7279        line 2
 7280        linXe 3
 7281        line 4
 7282        liˇne 5
 7283    "});
 7284
 7285    // Undo formatting
 7286    cx.update_editor(|editor, window, cx| {
 7287        editor.undo(&Default::default(), window, cx);
 7288    });
 7289
 7290    // Verify cursor moved back to position after edit
 7291    cx.assert_editor_state(indoc! {"
 7292        line 1
 7293        line 2
 7294        linXˇe 3
 7295        line 4
 7296        line 5
 7297    "});
 7298}
 7299
 7300#[gpui::test]
 7301async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7302    init_test(cx, |_| {});
 7303
 7304    let mut cx = EditorTestContext::new(cx).await;
 7305
 7306    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7307    cx.update_editor(|editor, window, cx| {
 7308        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7309    });
 7310
 7311    cx.set_state(indoc! {"
 7312        line 1
 7313        line 2
 7314        linˇe 3
 7315        line 4
 7316        line 5
 7317        line 6
 7318        line 7
 7319        line 8
 7320        line 9
 7321        line 10
 7322    "});
 7323
 7324    let snapshot = cx.buffer_snapshot();
 7325    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7326
 7327    cx.update(|_, cx| {
 7328        provider.update(cx, |provider, _| {
 7329            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7330                id: None,
 7331                edits: vec![(edit_position..edit_position, "X".into())],
 7332                edit_preview: None,
 7333            }))
 7334        })
 7335    });
 7336
 7337    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7338    cx.update_editor(|editor, window, cx| {
 7339        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7340    });
 7341
 7342    cx.assert_editor_state(indoc! {"
 7343        line 1
 7344        line 2
 7345        lineXˇ 3
 7346        line 4
 7347        line 5
 7348        line 6
 7349        line 7
 7350        line 8
 7351        line 9
 7352        line 10
 7353    "});
 7354
 7355    cx.update_editor(|editor, window, cx| {
 7356        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7357            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7358        });
 7359    });
 7360
 7361    cx.assert_editor_state(indoc! {"
 7362        line 1
 7363        line 2
 7364        lineX 3
 7365        line 4
 7366        line 5
 7367        line 6
 7368        line 7
 7369        line 8
 7370        line 9
 7371        liˇne 10
 7372    "});
 7373
 7374    cx.update_editor(|editor, window, cx| {
 7375        editor.undo(&Default::default(), window, cx);
 7376    });
 7377
 7378    cx.assert_editor_state(indoc! {"
 7379        line 1
 7380        line 2
 7381        lineˇ 3
 7382        line 4
 7383        line 5
 7384        line 6
 7385        line 7
 7386        line 8
 7387        line 9
 7388        line 10
 7389    "});
 7390}
 7391
 7392#[gpui::test]
 7393async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7394    init_test(cx, |_| {});
 7395
 7396    let mut cx = EditorTestContext::new(cx).await;
 7397    cx.set_state(
 7398        r#"let foo = 2;
 7399lˇet foo = 2;
 7400let fooˇ = 2;
 7401let foo = 2;
 7402let foo = ˇ2;"#,
 7403    );
 7404
 7405    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7406        .unwrap();
 7407    cx.assert_editor_state(
 7408        r#"let foo = 2;
 7409«letˇ» foo = 2;
 7410let «fooˇ» = 2;
 7411let foo = 2;
 7412let foo = «2ˇ»;"#,
 7413    );
 7414
 7415    // noop for multiple selections with different contents
 7416    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7417        .unwrap();
 7418    cx.assert_editor_state(
 7419        r#"let foo = 2;
 7420«letˇ» foo = 2;
 7421let «fooˇ» = 2;
 7422let foo = 2;
 7423let foo = «2ˇ»;"#,
 7424    );
 7425
 7426    // Test last selection direction should be preserved
 7427    cx.set_state(
 7428        r#"let foo = 2;
 7429let foo = 2;
 7430let «fooˇ» = 2;
 7431let «ˇfoo» = 2;
 7432let foo = 2;"#,
 7433    );
 7434
 7435    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7436        .unwrap();
 7437    cx.assert_editor_state(
 7438        r#"let foo = 2;
 7439let foo = 2;
 7440let «fooˇ» = 2;
 7441let «ˇfoo» = 2;
 7442let «ˇfoo» = 2;"#,
 7443    );
 7444}
 7445
 7446#[gpui::test]
 7447async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7448    init_test(cx, |_| {});
 7449
 7450    let mut cx =
 7451        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7452
 7453    cx.assert_editor_state(indoc! {"
 7454        ˇbbb
 7455        ccc
 7456
 7457        bbb
 7458        ccc
 7459        "});
 7460    cx.dispatch_action(SelectPrevious::default());
 7461    cx.assert_editor_state(indoc! {"
 7462                «bbbˇ»
 7463                ccc
 7464
 7465                bbb
 7466                ccc
 7467                "});
 7468    cx.dispatch_action(SelectPrevious::default());
 7469    cx.assert_editor_state(indoc! {"
 7470                «bbbˇ»
 7471                ccc
 7472
 7473                «bbbˇ»
 7474                ccc
 7475                "});
 7476}
 7477
 7478#[gpui::test]
 7479async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7480    init_test(cx, |_| {});
 7481
 7482    let mut cx = EditorTestContext::new(cx).await;
 7483    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7484
 7485    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7486        .unwrap();
 7487    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7492
 7493    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7494    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7495
 7496    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7497    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7498
 7499    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7500        .unwrap();
 7501    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7502
 7503    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7504        .unwrap();
 7505    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7506}
 7507
 7508#[gpui::test]
 7509async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7510    init_test(cx, |_| {});
 7511
 7512    let mut cx = EditorTestContext::new(cx).await;
 7513    cx.set_state("");
 7514
 7515    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7516        .unwrap();
 7517    cx.assert_editor_state("«aˇ»");
 7518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7519        .unwrap();
 7520    cx.assert_editor_state("«aˇ»");
 7521}
 7522
 7523#[gpui::test]
 7524async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7525    init_test(cx, |_| {});
 7526
 7527    let mut cx = EditorTestContext::new(cx).await;
 7528    cx.set_state(
 7529        r#"let foo = 2;
 7530lˇet foo = 2;
 7531let fooˇ = 2;
 7532let foo = 2;
 7533let foo = ˇ2;"#,
 7534    );
 7535
 7536    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7537        .unwrap();
 7538    cx.assert_editor_state(
 7539        r#"let foo = 2;
 7540«letˇ» foo = 2;
 7541let «fooˇ» = 2;
 7542let foo = 2;
 7543let foo = «2ˇ»;"#,
 7544    );
 7545
 7546    // noop for multiple selections with different contents
 7547    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7548        .unwrap();
 7549    cx.assert_editor_state(
 7550        r#"let foo = 2;
 7551«letˇ» foo = 2;
 7552let «fooˇ» = 2;
 7553let foo = 2;
 7554let foo = «2ˇ»;"#,
 7555    );
 7556}
 7557
 7558#[gpui::test]
 7559async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7560    init_test(cx, |_| {});
 7561
 7562    let mut cx = EditorTestContext::new(cx).await;
 7563    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7564
 7565    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7566        .unwrap();
 7567    // selection direction is preserved
 7568    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7569
 7570    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7571        .unwrap();
 7572    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7573
 7574    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7575    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7576
 7577    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7578    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7579
 7580    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7581        .unwrap();
 7582    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7583
 7584    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7585        .unwrap();
 7586    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7587}
 7588
 7589#[gpui::test]
 7590async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7591    init_test(cx, |_| {});
 7592
 7593    let language = Arc::new(Language::new(
 7594        LanguageConfig::default(),
 7595        Some(tree_sitter_rust::LANGUAGE.into()),
 7596    ));
 7597
 7598    let text = r#"
 7599        use mod1::mod2::{mod3, mod4};
 7600
 7601        fn fn_1(param1: bool, param2: &str) {
 7602            let var1 = "text";
 7603        }
 7604    "#
 7605    .unindent();
 7606
 7607    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7608    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7609    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7610
 7611    editor
 7612        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7613        .await;
 7614
 7615    editor.update_in(cx, |editor, window, cx| {
 7616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7617            s.select_display_ranges([
 7618                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7619                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7620                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7621            ]);
 7622        });
 7623        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7624    });
 7625    editor.update(cx, |editor, cx| {
 7626        assert_text_with_selections(
 7627            editor,
 7628            indoc! {r#"
 7629                use mod1::mod2::{mod3, «mod4ˇ»};
 7630
 7631                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7632                    let var1 = "«ˇtext»";
 7633                }
 7634            "#},
 7635            cx,
 7636        );
 7637    });
 7638
 7639    editor.update_in(cx, |editor, window, cx| {
 7640        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7641    });
 7642    editor.update(cx, |editor, cx| {
 7643        assert_text_with_selections(
 7644            editor,
 7645            indoc! {r#"
 7646                use mod1::mod2::«{mod3, mod4}ˇ»;
 7647
 7648                «ˇfn fn_1(param1: bool, param2: &str) {
 7649                    let var1 = "text";
 7650 7651            "#},
 7652            cx,
 7653        );
 7654    });
 7655
 7656    editor.update_in(cx, |editor, window, cx| {
 7657        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7658    });
 7659    assert_eq!(
 7660        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7661        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7662    );
 7663
 7664    // Trying to expand the selected syntax node one more time has no effect.
 7665    editor.update_in(cx, |editor, window, cx| {
 7666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7667    });
 7668    assert_eq!(
 7669        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7670        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7671    );
 7672
 7673    editor.update_in(cx, |editor, window, cx| {
 7674        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7675    });
 7676    editor.update(cx, |editor, cx| {
 7677        assert_text_with_selections(
 7678            editor,
 7679            indoc! {r#"
 7680                use mod1::mod2::«{mod3, mod4}ˇ»;
 7681
 7682                «ˇfn fn_1(param1: bool, param2: &str) {
 7683                    let var1 = "text";
 7684 7685            "#},
 7686            cx,
 7687        );
 7688    });
 7689
 7690    editor.update_in(cx, |editor, window, cx| {
 7691        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7692    });
 7693    editor.update(cx, |editor, cx| {
 7694        assert_text_with_selections(
 7695            editor,
 7696            indoc! {r#"
 7697                use mod1::mod2::{mod3, «mod4ˇ»};
 7698
 7699                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7700                    let var1 = "«ˇtext»";
 7701                }
 7702            "#},
 7703            cx,
 7704        );
 7705    });
 7706
 7707    editor.update_in(cx, |editor, window, cx| {
 7708        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7709    });
 7710    editor.update(cx, |editor, cx| {
 7711        assert_text_with_selections(
 7712            editor,
 7713            indoc! {r#"
 7714                use mod1::mod2::{mod3, mo«ˇ»d4};
 7715
 7716                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7717                    let var1 = "te«ˇ»xt";
 7718                }
 7719            "#},
 7720            cx,
 7721        );
 7722    });
 7723
 7724    // Trying to shrink the selected syntax node one more time has no effect.
 7725    editor.update_in(cx, |editor, window, cx| {
 7726        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7727    });
 7728    editor.update_in(cx, |editor, _, cx| {
 7729        assert_text_with_selections(
 7730            editor,
 7731            indoc! {r#"
 7732                use mod1::mod2::{mod3, mo«ˇ»d4};
 7733
 7734                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7735                    let var1 = "te«ˇ»xt";
 7736                }
 7737            "#},
 7738            cx,
 7739        );
 7740    });
 7741
 7742    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7743    // a fold.
 7744    editor.update_in(cx, |editor, window, cx| {
 7745        editor.fold_creases(
 7746            vec![
 7747                Crease::simple(
 7748                    Point::new(0, 21)..Point::new(0, 24),
 7749                    FoldPlaceholder::test(),
 7750                ),
 7751                Crease::simple(
 7752                    Point::new(3, 20)..Point::new(3, 22),
 7753                    FoldPlaceholder::test(),
 7754                ),
 7755            ],
 7756            true,
 7757            window,
 7758            cx,
 7759        );
 7760        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7761    });
 7762    editor.update(cx, |editor, cx| {
 7763        assert_text_with_selections(
 7764            editor,
 7765            indoc! {r#"
 7766                use mod1::mod2::«{mod3, mod4}ˇ»;
 7767
 7768                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7769                    let var1 = "«ˇtext»";
 7770                }
 7771            "#},
 7772            cx,
 7773        );
 7774    });
 7775}
 7776
 7777#[gpui::test]
 7778async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7779    init_test(cx, |_| {});
 7780
 7781    let language = Arc::new(Language::new(
 7782        LanguageConfig::default(),
 7783        Some(tree_sitter_rust::LANGUAGE.into()),
 7784    ));
 7785
 7786    let text = "let a = 2;";
 7787
 7788    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7789    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7790    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7791
 7792    editor
 7793        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7794        .await;
 7795
 7796    // Test case 1: Cursor at end of word
 7797    editor.update_in(cx, |editor, window, cx| {
 7798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7799            s.select_display_ranges([
 7800                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7801            ]);
 7802        });
 7803    });
 7804    editor.update(cx, |editor, cx| {
 7805        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7806    });
 7807    editor.update_in(cx, |editor, window, cx| {
 7808        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7809    });
 7810    editor.update(cx, |editor, cx| {
 7811        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7812    });
 7813    editor.update_in(cx, |editor, window, cx| {
 7814        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7815    });
 7816    editor.update(cx, |editor, cx| {
 7817        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7818    });
 7819
 7820    // Test case 2: Cursor at end of statement
 7821    editor.update_in(cx, |editor, window, cx| {
 7822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7823            s.select_display_ranges([
 7824                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7825            ]);
 7826        });
 7827    });
 7828    editor.update(cx, |editor, cx| {
 7829        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7830    });
 7831    editor.update_in(cx, |editor, window, cx| {
 7832        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7833    });
 7834    editor.update(cx, |editor, cx| {
 7835        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7836    });
 7837}
 7838
 7839#[gpui::test]
 7840async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7841    init_test(cx, |_| {});
 7842
 7843    let language = Arc::new(Language::new(
 7844        LanguageConfig::default(),
 7845        Some(tree_sitter_rust::LANGUAGE.into()),
 7846    ));
 7847
 7848    let text = r#"
 7849        use mod1::mod2::{mod3, mod4};
 7850
 7851        fn fn_1(param1: bool, param2: &str) {
 7852            let var1 = "hello world";
 7853        }
 7854    "#
 7855    .unindent();
 7856
 7857    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7858    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7859    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7860
 7861    editor
 7862        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7863        .await;
 7864
 7865    // Test 1: Cursor on a letter of a string word
 7866    editor.update_in(cx, |editor, window, cx| {
 7867        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7868            s.select_display_ranges([
 7869                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7870            ]);
 7871        });
 7872    });
 7873    editor.update_in(cx, |editor, window, cx| {
 7874        assert_text_with_selections(
 7875            editor,
 7876            indoc! {r#"
 7877                use mod1::mod2::{mod3, mod4};
 7878
 7879                fn fn_1(param1: bool, param2: &str) {
 7880                    let var1 = "hˇello world";
 7881                }
 7882            "#},
 7883            cx,
 7884        );
 7885        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7886        assert_text_with_selections(
 7887            editor,
 7888            indoc! {r#"
 7889                use mod1::mod2::{mod3, mod4};
 7890
 7891                fn fn_1(param1: bool, param2: &str) {
 7892                    let var1 = "«ˇhello» world";
 7893                }
 7894            "#},
 7895            cx,
 7896        );
 7897    });
 7898
 7899    // Test 2: Partial selection within a word
 7900    editor.update_in(cx, |editor, window, cx| {
 7901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7902            s.select_display_ranges([
 7903                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7904            ]);
 7905        });
 7906    });
 7907    editor.update_in(cx, |editor, window, cx| {
 7908        assert_text_with_selections(
 7909            editor,
 7910            indoc! {r#"
 7911                use mod1::mod2::{mod3, mod4};
 7912
 7913                fn fn_1(param1: bool, param2: &str) {
 7914                    let var1 = "h«elˇ»lo world";
 7915                }
 7916            "#},
 7917            cx,
 7918        );
 7919        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7920        assert_text_with_selections(
 7921            editor,
 7922            indoc! {r#"
 7923                use mod1::mod2::{mod3, mod4};
 7924
 7925                fn fn_1(param1: bool, param2: &str) {
 7926                    let var1 = "«ˇhello» world";
 7927                }
 7928            "#},
 7929            cx,
 7930        );
 7931    });
 7932
 7933    // Test 3: Complete word already selected
 7934    editor.update_in(cx, |editor, window, cx| {
 7935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7936            s.select_display_ranges([
 7937                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7938            ]);
 7939        });
 7940    });
 7941    editor.update_in(cx, |editor, window, cx| {
 7942        assert_text_with_selections(
 7943            editor,
 7944            indoc! {r#"
 7945                use mod1::mod2::{mod3, mod4};
 7946
 7947                fn fn_1(param1: bool, param2: &str) {
 7948                    let var1 = "«helloˇ» world";
 7949                }
 7950            "#},
 7951            cx,
 7952        );
 7953        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7954        assert_text_with_selections(
 7955            editor,
 7956            indoc! {r#"
 7957                use mod1::mod2::{mod3, mod4};
 7958
 7959                fn fn_1(param1: bool, param2: &str) {
 7960                    let var1 = "«hello worldˇ»";
 7961                }
 7962            "#},
 7963            cx,
 7964        );
 7965    });
 7966
 7967    // Test 4: Selection spanning across words
 7968    editor.update_in(cx, |editor, window, cx| {
 7969        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7970            s.select_display_ranges([
 7971                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7972            ]);
 7973        });
 7974    });
 7975    editor.update_in(cx, |editor, window, cx| {
 7976        assert_text_with_selections(
 7977            editor,
 7978            indoc! {r#"
 7979                use mod1::mod2::{mod3, mod4};
 7980
 7981                fn fn_1(param1: bool, param2: &str) {
 7982                    let var1 = "hel«lo woˇ»rld";
 7983                }
 7984            "#},
 7985            cx,
 7986        );
 7987        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7988        assert_text_with_selections(
 7989            editor,
 7990            indoc! {r#"
 7991                use mod1::mod2::{mod3, mod4};
 7992
 7993                fn fn_1(param1: bool, param2: &str) {
 7994                    let var1 = "«ˇhello world»";
 7995                }
 7996            "#},
 7997            cx,
 7998        );
 7999    });
 8000
 8001    // Test 5: Expansion beyond string
 8002    editor.update_in(cx, |editor, window, cx| {
 8003        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8004        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8005        assert_text_with_selections(
 8006            editor,
 8007            indoc! {r#"
 8008                use mod1::mod2::{mod3, mod4};
 8009
 8010                fn fn_1(param1: bool, param2: &str) {
 8011                    «ˇlet var1 = "hello world";»
 8012                }
 8013            "#},
 8014            cx,
 8015        );
 8016    });
 8017}
 8018
 8019#[gpui::test]
 8020async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8021    init_test(cx, |_| {});
 8022
 8023    let mut cx = EditorTestContext::new(cx).await;
 8024
 8025    let language = Arc::new(Language::new(
 8026        LanguageConfig::default(),
 8027        Some(tree_sitter_rust::LANGUAGE.into()),
 8028    ));
 8029
 8030    cx.update_buffer(|buffer, cx| {
 8031        buffer.set_language(Some(language), cx);
 8032    });
 8033
 8034    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8035    cx.update_editor(|editor, window, cx| {
 8036        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8037    });
 8038
 8039    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8040}
 8041
 8042#[gpui::test]
 8043async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8044    init_test(cx, |_| {});
 8045
 8046    let base_text = r#"
 8047        impl A {
 8048            // this is an uncommitted comment
 8049
 8050            fn b() {
 8051                c();
 8052            }
 8053
 8054            // this is another uncommitted comment
 8055
 8056            fn d() {
 8057                // e
 8058                // f
 8059            }
 8060        }
 8061
 8062        fn g() {
 8063            // h
 8064        }
 8065    "#
 8066    .unindent();
 8067
 8068    let text = r#"
 8069        ˇimpl A {
 8070
 8071            fn b() {
 8072                c();
 8073            }
 8074
 8075            fn d() {
 8076                // e
 8077                // f
 8078            }
 8079        }
 8080
 8081        fn g() {
 8082            // h
 8083        }
 8084    "#
 8085    .unindent();
 8086
 8087    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8088    cx.set_state(&text);
 8089    cx.set_head_text(&base_text);
 8090    cx.update_editor(|editor, window, cx| {
 8091        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8092    });
 8093
 8094    cx.assert_state_with_diff(
 8095        "
 8096        ˇimpl A {
 8097      -     // this is an uncommitted comment
 8098
 8099            fn b() {
 8100                c();
 8101            }
 8102
 8103      -     // this is another uncommitted comment
 8104      -
 8105            fn d() {
 8106                // e
 8107                // f
 8108            }
 8109        }
 8110
 8111        fn g() {
 8112            // h
 8113        }
 8114    "
 8115        .unindent(),
 8116    );
 8117
 8118    let expected_display_text = "
 8119        impl A {
 8120            // this is an uncommitted comment
 8121
 8122            fn b() {
 8123 8124            }
 8125
 8126            // this is another uncommitted comment
 8127
 8128            fn d() {
 8129 8130            }
 8131        }
 8132
 8133        fn g() {
 8134 8135        }
 8136        "
 8137    .unindent();
 8138
 8139    cx.update_editor(|editor, window, cx| {
 8140        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8141        assert_eq!(editor.display_text(cx), expected_display_text);
 8142    });
 8143}
 8144
 8145#[gpui::test]
 8146async fn test_autoindent(cx: &mut TestAppContext) {
 8147    init_test(cx, |_| {});
 8148
 8149    let language = Arc::new(
 8150        Language::new(
 8151            LanguageConfig {
 8152                brackets: BracketPairConfig {
 8153                    pairs: vec![
 8154                        BracketPair {
 8155                            start: "{".to_string(),
 8156                            end: "}".to_string(),
 8157                            close: false,
 8158                            surround: false,
 8159                            newline: true,
 8160                        },
 8161                        BracketPair {
 8162                            start: "(".to_string(),
 8163                            end: ")".to_string(),
 8164                            close: false,
 8165                            surround: false,
 8166                            newline: true,
 8167                        },
 8168                    ],
 8169                    ..Default::default()
 8170                },
 8171                ..Default::default()
 8172            },
 8173            Some(tree_sitter_rust::LANGUAGE.into()),
 8174        )
 8175        .with_indents_query(
 8176            r#"
 8177                (_ "(" ")" @end) @indent
 8178                (_ "{" "}" @end) @indent
 8179            "#,
 8180        )
 8181        .unwrap(),
 8182    );
 8183
 8184    let text = "fn a() {}";
 8185
 8186    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8187    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8188    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8189    editor
 8190        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8191        .await;
 8192
 8193    editor.update_in(cx, |editor, window, cx| {
 8194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8195            s.select_ranges([5..5, 8..8, 9..9])
 8196        });
 8197        editor.newline(&Newline, window, cx);
 8198        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8199        assert_eq!(
 8200            editor.selections.ranges(cx),
 8201            &[
 8202                Point::new(1, 4)..Point::new(1, 4),
 8203                Point::new(3, 4)..Point::new(3, 4),
 8204                Point::new(5, 0)..Point::new(5, 0)
 8205            ]
 8206        );
 8207    });
 8208}
 8209
 8210#[gpui::test]
 8211async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8212    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8213
 8214    let language = Arc::new(
 8215        Language::new(
 8216            LanguageConfig {
 8217                brackets: BracketPairConfig {
 8218                    pairs: vec![
 8219                        BracketPair {
 8220                            start: "{".to_string(),
 8221                            end: "}".to_string(),
 8222                            close: false,
 8223                            surround: false,
 8224                            newline: true,
 8225                        },
 8226                        BracketPair {
 8227                            start: "(".to_string(),
 8228                            end: ")".to_string(),
 8229                            close: false,
 8230                            surround: false,
 8231                            newline: true,
 8232                        },
 8233                    ],
 8234                    ..Default::default()
 8235                },
 8236                ..Default::default()
 8237            },
 8238            Some(tree_sitter_rust::LANGUAGE.into()),
 8239        )
 8240        .with_indents_query(
 8241            r#"
 8242                (_ "(" ")" @end) @indent
 8243                (_ "{" "}" @end) @indent
 8244            "#,
 8245        )
 8246        .unwrap(),
 8247    );
 8248
 8249    let text = "fn a() {}";
 8250
 8251    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8252    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8253    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8254    editor
 8255        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8256        .await;
 8257
 8258    editor.update_in(cx, |editor, window, cx| {
 8259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8260            s.select_ranges([5..5, 8..8, 9..9])
 8261        });
 8262        editor.newline(&Newline, window, cx);
 8263        assert_eq!(
 8264            editor.text(cx),
 8265            indoc!(
 8266                "
 8267                fn a(
 8268
 8269                ) {
 8270
 8271                }
 8272                "
 8273            )
 8274        );
 8275        assert_eq!(
 8276            editor.selections.ranges(cx),
 8277            &[
 8278                Point::new(1, 0)..Point::new(1, 0),
 8279                Point::new(3, 0)..Point::new(3, 0),
 8280                Point::new(5, 0)..Point::new(5, 0)
 8281            ]
 8282        );
 8283    });
 8284}
 8285
 8286#[gpui::test]
 8287async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8288    init_test(cx, |settings| {
 8289        settings.defaults.auto_indent = Some(true);
 8290        settings.languages.0.insert(
 8291            "python".into(),
 8292            LanguageSettingsContent {
 8293                auto_indent: Some(false),
 8294                ..Default::default()
 8295            },
 8296        );
 8297    });
 8298
 8299    let mut cx = EditorTestContext::new(cx).await;
 8300
 8301    let injected_language = Arc::new(
 8302        Language::new(
 8303            LanguageConfig {
 8304                brackets: BracketPairConfig {
 8305                    pairs: vec![
 8306                        BracketPair {
 8307                            start: "{".to_string(),
 8308                            end: "}".to_string(),
 8309                            close: false,
 8310                            surround: false,
 8311                            newline: true,
 8312                        },
 8313                        BracketPair {
 8314                            start: "(".to_string(),
 8315                            end: ")".to_string(),
 8316                            close: true,
 8317                            surround: false,
 8318                            newline: true,
 8319                        },
 8320                    ],
 8321                    ..Default::default()
 8322                },
 8323                name: "python".into(),
 8324                ..Default::default()
 8325            },
 8326            Some(tree_sitter_python::LANGUAGE.into()),
 8327        )
 8328        .with_indents_query(
 8329            r#"
 8330                (_ "(" ")" @end) @indent
 8331                (_ "{" "}" @end) @indent
 8332            "#,
 8333        )
 8334        .unwrap(),
 8335    );
 8336
 8337    let language = Arc::new(
 8338        Language::new(
 8339            LanguageConfig {
 8340                brackets: BracketPairConfig {
 8341                    pairs: vec![
 8342                        BracketPair {
 8343                            start: "{".to_string(),
 8344                            end: "}".to_string(),
 8345                            close: false,
 8346                            surround: false,
 8347                            newline: true,
 8348                        },
 8349                        BracketPair {
 8350                            start: "(".to_string(),
 8351                            end: ")".to_string(),
 8352                            close: true,
 8353                            surround: false,
 8354                            newline: true,
 8355                        },
 8356                    ],
 8357                    ..Default::default()
 8358                },
 8359                name: LanguageName::new("rust"),
 8360                ..Default::default()
 8361            },
 8362            Some(tree_sitter_rust::LANGUAGE.into()),
 8363        )
 8364        .with_indents_query(
 8365            r#"
 8366                (_ "(" ")" @end) @indent
 8367                (_ "{" "}" @end) @indent
 8368            "#,
 8369        )
 8370        .unwrap()
 8371        .with_injection_query(
 8372            r#"
 8373            (macro_invocation
 8374                macro: (identifier) @_macro_name
 8375                (token_tree) @injection.content
 8376                (#set! injection.language "python"))
 8377           "#,
 8378        )
 8379        .unwrap(),
 8380    );
 8381
 8382    cx.language_registry().add(injected_language);
 8383    cx.language_registry().add(language.clone());
 8384
 8385    cx.update_buffer(|buffer, cx| {
 8386        buffer.set_language(Some(language), cx);
 8387    });
 8388
 8389    cx.set_state(r#"struct A {ˇ}"#);
 8390
 8391    cx.update_editor(|editor, window, cx| {
 8392        editor.newline(&Default::default(), window, cx);
 8393    });
 8394
 8395    cx.assert_editor_state(indoc!(
 8396        "struct A {
 8397            ˇ
 8398        }"
 8399    ));
 8400
 8401    cx.set_state(r#"select_biased!(ˇ)"#);
 8402
 8403    cx.update_editor(|editor, window, cx| {
 8404        editor.newline(&Default::default(), window, cx);
 8405        editor.handle_input("def ", window, cx);
 8406        editor.handle_input("(", window, cx);
 8407        editor.newline(&Default::default(), window, cx);
 8408        editor.handle_input("a", window, cx);
 8409    });
 8410
 8411    cx.assert_editor_state(indoc!(
 8412        "select_biased!(
 8413        def (
 8414 8415        )
 8416        )"
 8417    ));
 8418}
 8419
 8420#[gpui::test]
 8421async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8422    init_test(cx, |_| {});
 8423
 8424    {
 8425        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8426        cx.set_state(indoc! {"
 8427            impl A {
 8428
 8429                fn b() {}
 8430
 8431            «fn c() {
 8432
 8433            }ˇ»
 8434            }
 8435        "});
 8436
 8437        cx.update_editor(|editor, window, cx| {
 8438            editor.autoindent(&Default::default(), window, cx);
 8439        });
 8440
 8441        cx.assert_editor_state(indoc! {"
 8442            impl A {
 8443
 8444                fn b() {}
 8445
 8446                «fn c() {
 8447
 8448                }ˇ»
 8449            }
 8450        "});
 8451    }
 8452
 8453    {
 8454        let mut cx = EditorTestContext::new_multibuffer(
 8455            cx,
 8456            [indoc! { "
 8457                impl A {
 8458                «
 8459                // a
 8460                fn b(){}
 8461                »
 8462                «
 8463                    }
 8464                    fn c(){}
 8465                »
 8466            "}],
 8467        );
 8468
 8469        let buffer = cx.update_editor(|editor, _, cx| {
 8470            let buffer = editor.buffer().update(cx, |buffer, _| {
 8471                buffer.all_buffers().iter().next().unwrap().clone()
 8472            });
 8473            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8474            buffer
 8475        });
 8476
 8477        cx.run_until_parked();
 8478        cx.update_editor(|editor, window, cx| {
 8479            editor.select_all(&Default::default(), window, cx);
 8480            editor.autoindent(&Default::default(), window, cx)
 8481        });
 8482        cx.run_until_parked();
 8483
 8484        cx.update(|_, cx| {
 8485            assert_eq!(
 8486                buffer.read(cx).text(),
 8487                indoc! { "
 8488                    impl A {
 8489
 8490                        // a
 8491                        fn b(){}
 8492
 8493
 8494                    }
 8495                    fn c(){}
 8496
 8497                " }
 8498            )
 8499        });
 8500    }
 8501}
 8502
 8503#[gpui::test]
 8504async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8505    init_test(cx, |_| {});
 8506
 8507    let mut cx = EditorTestContext::new(cx).await;
 8508
 8509    let language = Arc::new(Language::new(
 8510        LanguageConfig {
 8511            brackets: BracketPairConfig {
 8512                pairs: vec![
 8513                    BracketPair {
 8514                        start: "{".to_string(),
 8515                        end: "}".to_string(),
 8516                        close: true,
 8517                        surround: true,
 8518                        newline: true,
 8519                    },
 8520                    BracketPair {
 8521                        start: "(".to_string(),
 8522                        end: ")".to_string(),
 8523                        close: true,
 8524                        surround: true,
 8525                        newline: true,
 8526                    },
 8527                    BracketPair {
 8528                        start: "/*".to_string(),
 8529                        end: " */".to_string(),
 8530                        close: true,
 8531                        surround: true,
 8532                        newline: true,
 8533                    },
 8534                    BracketPair {
 8535                        start: "[".to_string(),
 8536                        end: "]".to_string(),
 8537                        close: false,
 8538                        surround: false,
 8539                        newline: true,
 8540                    },
 8541                    BracketPair {
 8542                        start: "\"".to_string(),
 8543                        end: "\"".to_string(),
 8544                        close: true,
 8545                        surround: true,
 8546                        newline: false,
 8547                    },
 8548                    BracketPair {
 8549                        start: "<".to_string(),
 8550                        end: ">".to_string(),
 8551                        close: false,
 8552                        surround: true,
 8553                        newline: true,
 8554                    },
 8555                ],
 8556                ..Default::default()
 8557            },
 8558            autoclose_before: "})]".to_string(),
 8559            ..Default::default()
 8560        },
 8561        Some(tree_sitter_rust::LANGUAGE.into()),
 8562    ));
 8563
 8564    cx.language_registry().add(language.clone());
 8565    cx.update_buffer(|buffer, cx| {
 8566        buffer.set_language(Some(language), cx);
 8567    });
 8568
 8569    cx.set_state(
 8570        &r#"
 8571            🏀ˇ
 8572            εˇ
 8573            ❤️ˇ
 8574        "#
 8575        .unindent(),
 8576    );
 8577
 8578    // autoclose multiple nested brackets at multiple cursors
 8579    cx.update_editor(|editor, window, cx| {
 8580        editor.handle_input("{", window, cx);
 8581        editor.handle_input("{", window, cx);
 8582        editor.handle_input("{", window, cx);
 8583    });
 8584    cx.assert_editor_state(
 8585        &"
 8586            🏀{{{ˇ}}}
 8587            ε{{{ˇ}}}
 8588            ❤️{{{ˇ}}}
 8589        "
 8590        .unindent(),
 8591    );
 8592
 8593    // insert a different closing bracket
 8594    cx.update_editor(|editor, window, cx| {
 8595        editor.handle_input(")", window, cx);
 8596    });
 8597    cx.assert_editor_state(
 8598        &"
 8599            🏀{{{)ˇ}}}
 8600            ε{{{)ˇ}}}
 8601            ❤️{{{)ˇ}}}
 8602        "
 8603        .unindent(),
 8604    );
 8605
 8606    // skip over the auto-closed brackets when typing a closing bracket
 8607    cx.update_editor(|editor, window, cx| {
 8608        editor.move_right(&MoveRight, window, cx);
 8609        editor.handle_input("}", window, cx);
 8610        editor.handle_input("}", window, cx);
 8611        editor.handle_input("}", window, cx);
 8612    });
 8613    cx.assert_editor_state(
 8614        &"
 8615            🏀{{{)}}}}ˇ
 8616            ε{{{)}}}}ˇ
 8617            ❤️{{{)}}}}ˇ
 8618        "
 8619        .unindent(),
 8620    );
 8621
 8622    // autoclose multi-character pairs
 8623    cx.set_state(
 8624        &"
 8625            ˇ
 8626            ˇ
 8627        "
 8628        .unindent(),
 8629    );
 8630    cx.update_editor(|editor, window, cx| {
 8631        editor.handle_input("/", window, cx);
 8632        editor.handle_input("*", window, cx);
 8633    });
 8634    cx.assert_editor_state(
 8635        &"
 8636            /*ˇ */
 8637            /*ˇ */
 8638        "
 8639        .unindent(),
 8640    );
 8641
 8642    // one cursor autocloses a multi-character pair, one cursor
 8643    // does not autoclose.
 8644    cx.set_state(
 8645        &"
 8646 8647            ˇ
 8648        "
 8649        .unindent(),
 8650    );
 8651    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8652    cx.assert_editor_state(
 8653        &"
 8654            /*ˇ */
 8655 8656        "
 8657        .unindent(),
 8658    );
 8659
 8660    // Don't autoclose if the next character isn't whitespace and isn't
 8661    // listed in the language's "autoclose_before" section.
 8662    cx.set_state("ˇa b");
 8663    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8664    cx.assert_editor_state("{ˇa b");
 8665
 8666    // Don't autoclose if `close` is false for the bracket pair
 8667    cx.set_state("ˇ");
 8668    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8669    cx.assert_editor_state("");
 8670
 8671    // Surround with brackets if text is selected
 8672    cx.set_state("«aˇ» b");
 8673    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8674    cx.assert_editor_state("{«aˇ»} b");
 8675
 8676    // Autoclose when not immediately after a word character
 8677    cx.set_state("a ˇ");
 8678    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8679    cx.assert_editor_state("a \"ˇ\"");
 8680
 8681    // Autoclose pair where the start and end characters are the same
 8682    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8683    cx.assert_editor_state("a \"\"ˇ");
 8684
 8685    // Don't autoclose when immediately after a word character
 8686    cx.set_state("");
 8687    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8688    cx.assert_editor_state("a\"ˇ");
 8689
 8690    // Do autoclose when after a non-word character
 8691    cx.set_state("");
 8692    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8693    cx.assert_editor_state("{\"ˇ\"");
 8694
 8695    // Non identical pairs autoclose regardless of preceding character
 8696    cx.set_state("");
 8697    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8698    cx.assert_editor_state("a{ˇ}");
 8699
 8700    // Don't autoclose pair if autoclose is disabled
 8701    cx.set_state("ˇ");
 8702    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8703    cx.assert_editor_state("");
 8704
 8705    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8706    cx.set_state("«aˇ» b");
 8707    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8708    cx.assert_editor_state("<«aˇ»> b");
 8709}
 8710
 8711#[gpui::test]
 8712async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8713    init_test(cx, |settings| {
 8714        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8715    });
 8716
 8717    let mut cx = EditorTestContext::new(cx).await;
 8718
 8719    let language = Arc::new(Language::new(
 8720        LanguageConfig {
 8721            brackets: BracketPairConfig {
 8722                pairs: vec![
 8723                    BracketPair {
 8724                        start: "{".to_string(),
 8725                        end: "}".to_string(),
 8726                        close: true,
 8727                        surround: true,
 8728                        newline: true,
 8729                    },
 8730                    BracketPair {
 8731                        start: "(".to_string(),
 8732                        end: ")".to_string(),
 8733                        close: true,
 8734                        surround: true,
 8735                        newline: true,
 8736                    },
 8737                    BracketPair {
 8738                        start: "[".to_string(),
 8739                        end: "]".to_string(),
 8740                        close: false,
 8741                        surround: false,
 8742                        newline: true,
 8743                    },
 8744                ],
 8745                ..Default::default()
 8746            },
 8747            autoclose_before: "})]".to_string(),
 8748            ..Default::default()
 8749        },
 8750        Some(tree_sitter_rust::LANGUAGE.into()),
 8751    ));
 8752
 8753    cx.language_registry().add(language.clone());
 8754    cx.update_buffer(|buffer, cx| {
 8755        buffer.set_language(Some(language), cx);
 8756    });
 8757
 8758    cx.set_state(
 8759        &"
 8760            ˇ
 8761            ˇ
 8762            ˇ
 8763        "
 8764        .unindent(),
 8765    );
 8766
 8767    // ensure only matching closing brackets are skipped over
 8768    cx.update_editor(|editor, window, cx| {
 8769        editor.handle_input("}", window, cx);
 8770        editor.move_left(&MoveLeft, window, cx);
 8771        editor.handle_input(")", window, cx);
 8772        editor.move_left(&MoveLeft, window, cx);
 8773    });
 8774    cx.assert_editor_state(
 8775        &"
 8776            ˇ)}
 8777            ˇ)}
 8778            ˇ)}
 8779        "
 8780        .unindent(),
 8781    );
 8782
 8783    // skip-over closing brackets at multiple cursors
 8784    cx.update_editor(|editor, window, cx| {
 8785        editor.handle_input(")", window, cx);
 8786        editor.handle_input("}", window, cx);
 8787    });
 8788    cx.assert_editor_state(
 8789        &"
 8790            )}ˇ
 8791            )}ˇ
 8792            )}ˇ
 8793        "
 8794        .unindent(),
 8795    );
 8796
 8797    // ignore non-close brackets
 8798    cx.update_editor(|editor, window, cx| {
 8799        editor.handle_input("]", window, cx);
 8800        editor.move_left(&MoveLeft, window, cx);
 8801        editor.handle_input("]", window, cx);
 8802    });
 8803    cx.assert_editor_state(
 8804        &"
 8805            )}]ˇ]
 8806            )}]ˇ]
 8807            )}]ˇ]
 8808        "
 8809        .unindent(),
 8810    );
 8811}
 8812
 8813#[gpui::test]
 8814async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8815    init_test(cx, |_| {});
 8816
 8817    let mut cx = EditorTestContext::new(cx).await;
 8818
 8819    let html_language = Arc::new(
 8820        Language::new(
 8821            LanguageConfig {
 8822                name: "HTML".into(),
 8823                brackets: BracketPairConfig {
 8824                    pairs: vec![
 8825                        BracketPair {
 8826                            start: "<".into(),
 8827                            end: ">".into(),
 8828                            close: true,
 8829                            ..Default::default()
 8830                        },
 8831                        BracketPair {
 8832                            start: "{".into(),
 8833                            end: "}".into(),
 8834                            close: true,
 8835                            ..Default::default()
 8836                        },
 8837                        BracketPair {
 8838                            start: "(".into(),
 8839                            end: ")".into(),
 8840                            close: true,
 8841                            ..Default::default()
 8842                        },
 8843                    ],
 8844                    ..Default::default()
 8845                },
 8846                autoclose_before: "})]>".into(),
 8847                ..Default::default()
 8848            },
 8849            Some(tree_sitter_html::LANGUAGE.into()),
 8850        )
 8851        .with_injection_query(
 8852            r#"
 8853            (script_element
 8854                (raw_text) @injection.content
 8855                (#set! injection.language "javascript"))
 8856            "#,
 8857        )
 8858        .unwrap(),
 8859    );
 8860
 8861    let javascript_language = Arc::new(Language::new(
 8862        LanguageConfig {
 8863            name: "JavaScript".into(),
 8864            brackets: BracketPairConfig {
 8865                pairs: vec![
 8866                    BracketPair {
 8867                        start: "/*".into(),
 8868                        end: " */".into(),
 8869                        close: true,
 8870                        ..Default::default()
 8871                    },
 8872                    BracketPair {
 8873                        start: "{".into(),
 8874                        end: "}".into(),
 8875                        close: true,
 8876                        ..Default::default()
 8877                    },
 8878                    BracketPair {
 8879                        start: "(".into(),
 8880                        end: ")".into(),
 8881                        close: true,
 8882                        ..Default::default()
 8883                    },
 8884                ],
 8885                ..Default::default()
 8886            },
 8887            autoclose_before: "})]>".into(),
 8888            ..Default::default()
 8889        },
 8890        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8891    ));
 8892
 8893    cx.language_registry().add(html_language.clone());
 8894    cx.language_registry().add(javascript_language);
 8895    cx.executor().run_until_parked();
 8896
 8897    cx.update_buffer(|buffer, cx| {
 8898        buffer.set_language(Some(html_language), cx);
 8899    });
 8900
 8901    cx.set_state(
 8902        &r#"
 8903            <body>ˇ
 8904                <script>
 8905                    var x = 1;ˇ
 8906                </script>
 8907            </body>ˇ
 8908        "#
 8909        .unindent(),
 8910    );
 8911
 8912    // Precondition: different languages are active at different locations.
 8913    cx.update_editor(|editor, window, cx| {
 8914        let snapshot = editor.snapshot(window, cx);
 8915        let cursors = editor.selections.ranges::<usize>(cx);
 8916        let languages = cursors
 8917            .iter()
 8918            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8919            .collect::<Vec<_>>();
 8920        assert_eq!(
 8921            languages,
 8922            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8923        );
 8924    });
 8925
 8926    // Angle brackets autoclose in HTML, but not JavaScript.
 8927    cx.update_editor(|editor, window, cx| {
 8928        editor.handle_input("<", window, cx);
 8929        editor.handle_input("a", window, cx);
 8930    });
 8931    cx.assert_editor_state(
 8932        &r#"
 8933            <body><aˇ>
 8934                <script>
 8935                    var x = 1;<aˇ
 8936                </script>
 8937            </body><aˇ>
 8938        "#
 8939        .unindent(),
 8940    );
 8941
 8942    // Curly braces and parens autoclose in both HTML and JavaScript.
 8943    cx.update_editor(|editor, window, cx| {
 8944        editor.handle_input(" b=", window, cx);
 8945        editor.handle_input("{", window, cx);
 8946        editor.handle_input("c", window, cx);
 8947        editor.handle_input("(", window, cx);
 8948    });
 8949    cx.assert_editor_state(
 8950        &r#"
 8951            <body><a b={c(ˇ)}>
 8952                <script>
 8953                    var x = 1;<a b={c(ˇ)}
 8954                </script>
 8955            </body><a b={c(ˇ)}>
 8956        "#
 8957        .unindent(),
 8958    );
 8959
 8960    // Brackets that were already autoclosed are skipped.
 8961    cx.update_editor(|editor, window, cx| {
 8962        editor.handle_input(")", window, cx);
 8963        editor.handle_input("d", window, cx);
 8964        editor.handle_input("}", window, cx);
 8965    });
 8966    cx.assert_editor_state(
 8967        &r#"
 8968            <body><a b={c()d}ˇ>
 8969                <script>
 8970                    var x = 1;<a b={c()d}ˇ
 8971                </script>
 8972            </body><a b={c()d}ˇ>
 8973        "#
 8974        .unindent(),
 8975    );
 8976    cx.update_editor(|editor, window, cx| {
 8977        editor.handle_input(">", window, cx);
 8978    });
 8979    cx.assert_editor_state(
 8980        &r#"
 8981            <body><a b={c()d}>ˇ
 8982                <script>
 8983                    var x = 1;<a b={c()d}>ˇ
 8984                </script>
 8985            </body><a b={c()d}>ˇ
 8986        "#
 8987        .unindent(),
 8988    );
 8989
 8990    // Reset
 8991    cx.set_state(
 8992        &r#"
 8993            <body>ˇ
 8994                <script>
 8995                    var x = 1;ˇ
 8996                </script>
 8997            </body>ˇ
 8998        "#
 8999        .unindent(),
 9000    );
 9001
 9002    cx.update_editor(|editor, window, cx| {
 9003        editor.handle_input("<", window, cx);
 9004    });
 9005    cx.assert_editor_state(
 9006        &r#"
 9007            <body><ˇ>
 9008                <script>
 9009                    var x = 1;<ˇ
 9010                </script>
 9011            </body><ˇ>
 9012        "#
 9013        .unindent(),
 9014    );
 9015
 9016    // When backspacing, the closing angle brackets are removed.
 9017    cx.update_editor(|editor, window, cx| {
 9018        editor.backspace(&Backspace, window, cx);
 9019    });
 9020    cx.assert_editor_state(
 9021        &r#"
 9022            <body>ˇ
 9023                <script>
 9024                    var x = 1;ˇ
 9025                </script>
 9026            </body>ˇ
 9027        "#
 9028        .unindent(),
 9029    );
 9030
 9031    // Block comments autoclose in JavaScript, but not HTML.
 9032    cx.update_editor(|editor, window, cx| {
 9033        editor.handle_input("/", window, cx);
 9034        editor.handle_input("*", window, cx);
 9035    });
 9036    cx.assert_editor_state(
 9037        &r#"
 9038            <body>/*ˇ
 9039                <script>
 9040                    var x = 1;/*ˇ */
 9041                </script>
 9042            </body>/*ˇ
 9043        "#
 9044        .unindent(),
 9045    );
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let mut cx = EditorTestContext::new(cx).await;
 9053
 9054    let rust_language = Arc::new(
 9055        Language::new(
 9056            LanguageConfig {
 9057                name: "Rust".into(),
 9058                brackets: serde_json::from_value(json!([
 9059                    { "start": "{", "end": "}", "close": true, "newline": true },
 9060                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9061                ]))
 9062                .unwrap(),
 9063                autoclose_before: "})]>".into(),
 9064                ..Default::default()
 9065            },
 9066            Some(tree_sitter_rust::LANGUAGE.into()),
 9067        )
 9068        .with_override_query("(string_literal) @string")
 9069        .unwrap(),
 9070    );
 9071
 9072    cx.language_registry().add(rust_language.clone());
 9073    cx.update_buffer(|buffer, cx| {
 9074        buffer.set_language(Some(rust_language), cx);
 9075    });
 9076
 9077    cx.set_state(
 9078        &r#"
 9079            let x = ˇ
 9080        "#
 9081        .unindent(),
 9082    );
 9083
 9084    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9085    cx.update_editor(|editor, window, cx| {
 9086        editor.handle_input("\"", window, cx);
 9087    });
 9088    cx.assert_editor_state(
 9089        &r#"
 9090            let x = "ˇ"
 9091        "#
 9092        .unindent(),
 9093    );
 9094
 9095    // Inserting another quotation mark. The cursor moves across the existing
 9096    // automatically-inserted quotation mark.
 9097    cx.update_editor(|editor, window, cx| {
 9098        editor.handle_input("\"", window, cx);
 9099    });
 9100    cx.assert_editor_state(
 9101        &r#"
 9102            let x = ""ˇ
 9103        "#
 9104        .unindent(),
 9105    );
 9106
 9107    // Reset
 9108    cx.set_state(
 9109        &r#"
 9110            let x = ˇ
 9111        "#
 9112        .unindent(),
 9113    );
 9114
 9115    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9116    cx.update_editor(|editor, window, cx| {
 9117        editor.handle_input("\"", window, cx);
 9118        editor.handle_input(" ", window, cx);
 9119        editor.move_left(&Default::default(), window, cx);
 9120        editor.handle_input("\\", window, cx);
 9121        editor.handle_input("\"", window, cx);
 9122    });
 9123    cx.assert_editor_state(
 9124        &r#"
 9125            let x = "\"ˇ "
 9126        "#
 9127        .unindent(),
 9128    );
 9129
 9130    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9131    // mark. Nothing is inserted.
 9132    cx.update_editor(|editor, window, cx| {
 9133        editor.move_right(&Default::default(), window, cx);
 9134        editor.handle_input("\"", window, cx);
 9135    });
 9136    cx.assert_editor_state(
 9137        &r#"
 9138            let x = "\" "ˇ
 9139        "#
 9140        .unindent(),
 9141    );
 9142}
 9143
 9144#[gpui::test]
 9145async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9146    init_test(cx, |_| {});
 9147
 9148    let language = Arc::new(Language::new(
 9149        LanguageConfig {
 9150            brackets: BracketPairConfig {
 9151                pairs: vec![
 9152                    BracketPair {
 9153                        start: "{".to_string(),
 9154                        end: "}".to_string(),
 9155                        close: true,
 9156                        surround: true,
 9157                        newline: true,
 9158                    },
 9159                    BracketPair {
 9160                        start: "/* ".to_string(),
 9161                        end: "*/".to_string(),
 9162                        close: true,
 9163                        surround: true,
 9164                        ..Default::default()
 9165                    },
 9166                ],
 9167                ..Default::default()
 9168            },
 9169            ..Default::default()
 9170        },
 9171        Some(tree_sitter_rust::LANGUAGE.into()),
 9172    ));
 9173
 9174    let text = r#"
 9175        a
 9176        b
 9177        c
 9178    "#
 9179    .unindent();
 9180
 9181    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9182    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9183    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9184    editor
 9185        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9186        .await;
 9187
 9188    editor.update_in(cx, |editor, window, cx| {
 9189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9190            s.select_display_ranges([
 9191                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9192                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9193                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9194            ])
 9195        });
 9196
 9197        editor.handle_input("{", window, cx);
 9198        editor.handle_input("{", window, cx);
 9199        editor.handle_input("{", window, cx);
 9200        assert_eq!(
 9201            editor.text(cx),
 9202            "
 9203                {{{a}}}
 9204                {{{b}}}
 9205                {{{c}}}
 9206            "
 9207            .unindent()
 9208        );
 9209        assert_eq!(
 9210            editor.selections.display_ranges(cx),
 9211            [
 9212                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9213                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9214                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9215            ]
 9216        );
 9217
 9218        editor.undo(&Undo, window, cx);
 9219        editor.undo(&Undo, window, cx);
 9220        editor.undo(&Undo, window, cx);
 9221        assert_eq!(
 9222            editor.text(cx),
 9223            "
 9224                a
 9225                b
 9226                c
 9227            "
 9228            .unindent()
 9229        );
 9230        assert_eq!(
 9231            editor.selections.display_ranges(cx),
 9232            [
 9233                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9234                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9235                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9236            ]
 9237        );
 9238
 9239        // Ensure inserting the first character of a multi-byte bracket pair
 9240        // doesn't surround the selections with the bracket.
 9241        editor.handle_input("/", window, cx);
 9242        assert_eq!(
 9243            editor.text(cx),
 9244            "
 9245                /
 9246                /
 9247                /
 9248            "
 9249            .unindent()
 9250        );
 9251        assert_eq!(
 9252            editor.selections.display_ranges(cx),
 9253            [
 9254                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9255                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9256                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9257            ]
 9258        );
 9259
 9260        editor.undo(&Undo, window, cx);
 9261        assert_eq!(
 9262            editor.text(cx),
 9263            "
 9264                a
 9265                b
 9266                c
 9267            "
 9268            .unindent()
 9269        );
 9270        assert_eq!(
 9271            editor.selections.display_ranges(cx),
 9272            [
 9273                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9274                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9275                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9276            ]
 9277        );
 9278
 9279        // Ensure inserting the last character of a multi-byte bracket pair
 9280        // doesn't surround the selections with the bracket.
 9281        editor.handle_input("*", window, cx);
 9282        assert_eq!(
 9283            editor.text(cx),
 9284            "
 9285                *
 9286                *
 9287                *
 9288            "
 9289            .unindent()
 9290        );
 9291        assert_eq!(
 9292            editor.selections.display_ranges(cx),
 9293            [
 9294                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9295                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9296                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9297            ]
 9298        );
 9299    });
 9300}
 9301
 9302#[gpui::test]
 9303async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9304    init_test(cx, |_| {});
 9305
 9306    let language = Arc::new(Language::new(
 9307        LanguageConfig {
 9308            brackets: BracketPairConfig {
 9309                pairs: vec![BracketPair {
 9310                    start: "{".to_string(),
 9311                    end: "}".to_string(),
 9312                    close: true,
 9313                    surround: true,
 9314                    newline: true,
 9315                }],
 9316                ..Default::default()
 9317            },
 9318            autoclose_before: "}".to_string(),
 9319            ..Default::default()
 9320        },
 9321        Some(tree_sitter_rust::LANGUAGE.into()),
 9322    ));
 9323
 9324    let text = r#"
 9325        a
 9326        b
 9327        c
 9328    "#
 9329    .unindent();
 9330
 9331    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9333    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9334    editor
 9335        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9336        .await;
 9337
 9338    editor.update_in(cx, |editor, window, cx| {
 9339        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9340            s.select_ranges([
 9341                Point::new(0, 1)..Point::new(0, 1),
 9342                Point::new(1, 1)..Point::new(1, 1),
 9343                Point::new(2, 1)..Point::new(2, 1),
 9344            ])
 9345        });
 9346
 9347        editor.handle_input("{", window, cx);
 9348        editor.handle_input("{", window, cx);
 9349        editor.handle_input("_", window, cx);
 9350        assert_eq!(
 9351            editor.text(cx),
 9352            "
 9353                a{{_}}
 9354                b{{_}}
 9355                c{{_}}
 9356            "
 9357            .unindent()
 9358        );
 9359        assert_eq!(
 9360            editor.selections.ranges::<Point>(cx),
 9361            [
 9362                Point::new(0, 4)..Point::new(0, 4),
 9363                Point::new(1, 4)..Point::new(1, 4),
 9364                Point::new(2, 4)..Point::new(2, 4)
 9365            ]
 9366        );
 9367
 9368        editor.backspace(&Default::default(), window, cx);
 9369        editor.backspace(&Default::default(), window, cx);
 9370        assert_eq!(
 9371            editor.text(cx),
 9372            "
 9373                a{}
 9374                b{}
 9375                c{}
 9376            "
 9377            .unindent()
 9378        );
 9379        assert_eq!(
 9380            editor.selections.ranges::<Point>(cx),
 9381            [
 9382                Point::new(0, 2)..Point::new(0, 2),
 9383                Point::new(1, 2)..Point::new(1, 2),
 9384                Point::new(2, 2)..Point::new(2, 2)
 9385            ]
 9386        );
 9387
 9388        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9389        assert_eq!(
 9390            editor.text(cx),
 9391            "
 9392                a
 9393                b
 9394                c
 9395            "
 9396            .unindent()
 9397        );
 9398        assert_eq!(
 9399            editor.selections.ranges::<Point>(cx),
 9400            [
 9401                Point::new(0, 1)..Point::new(0, 1),
 9402                Point::new(1, 1)..Point::new(1, 1),
 9403                Point::new(2, 1)..Point::new(2, 1)
 9404            ]
 9405        );
 9406    });
 9407}
 9408
 9409#[gpui::test]
 9410async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9411    init_test(cx, |settings| {
 9412        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9413    });
 9414
 9415    let mut cx = EditorTestContext::new(cx).await;
 9416
 9417    let language = Arc::new(Language::new(
 9418        LanguageConfig {
 9419            brackets: BracketPairConfig {
 9420                pairs: vec![
 9421                    BracketPair {
 9422                        start: "{".to_string(),
 9423                        end: "}".to_string(),
 9424                        close: true,
 9425                        surround: true,
 9426                        newline: true,
 9427                    },
 9428                    BracketPair {
 9429                        start: "(".to_string(),
 9430                        end: ")".to_string(),
 9431                        close: true,
 9432                        surround: true,
 9433                        newline: true,
 9434                    },
 9435                    BracketPair {
 9436                        start: "[".to_string(),
 9437                        end: "]".to_string(),
 9438                        close: false,
 9439                        surround: true,
 9440                        newline: true,
 9441                    },
 9442                ],
 9443                ..Default::default()
 9444            },
 9445            autoclose_before: "})]".to_string(),
 9446            ..Default::default()
 9447        },
 9448        Some(tree_sitter_rust::LANGUAGE.into()),
 9449    ));
 9450
 9451    cx.language_registry().add(language.clone());
 9452    cx.update_buffer(|buffer, cx| {
 9453        buffer.set_language(Some(language), cx);
 9454    });
 9455
 9456    cx.set_state(
 9457        &"
 9458            {(ˇ)}
 9459            [[ˇ]]
 9460            {(ˇ)}
 9461        "
 9462        .unindent(),
 9463    );
 9464
 9465    cx.update_editor(|editor, window, cx| {
 9466        editor.backspace(&Default::default(), window, cx);
 9467        editor.backspace(&Default::default(), window, cx);
 9468    });
 9469
 9470    cx.assert_editor_state(
 9471        &"
 9472            ˇ
 9473            ˇ]]
 9474            ˇ
 9475        "
 9476        .unindent(),
 9477    );
 9478
 9479    cx.update_editor(|editor, window, cx| {
 9480        editor.handle_input("{", window, cx);
 9481        editor.handle_input("{", window, cx);
 9482        editor.move_right(&MoveRight, window, cx);
 9483        editor.move_right(&MoveRight, window, cx);
 9484        editor.move_left(&MoveLeft, window, cx);
 9485        editor.move_left(&MoveLeft, window, cx);
 9486        editor.backspace(&Default::default(), window, cx);
 9487    });
 9488
 9489    cx.assert_editor_state(
 9490        &"
 9491            {ˇ}
 9492            {ˇ}]]
 9493            {ˇ}
 9494        "
 9495        .unindent(),
 9496    );
 9497
 9498    cx.update_editor(|editor, window, cx| {
 9499        editor.backspace(&Default::default(), window, cx);
 9500    });
 9501
 9502    cx.assert_editor_state(
 9503        &"
 9504            ˇ
 9505            ˇ]]
 9506            ˇ
 9507        "
 9508        .unindent(),
 9509    );
 9510}
 9511
 9512#[gpui::test]
 9513async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9514    init_test(cx, |_| {});
 9515
 9516    let language = Arc::new(Language::new(
 9517        LanguageConfig::default(),
 9518        Some(tree_sitter_rust::LANGUAGE.into()),
 9519    ));
 9520
 9521    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9522    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9523    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9524    editor
 9525        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9526        .await;
 9527
 9528    editor.update_in(cx, |editor, window, cx| {
 9529        editor.set_auto_replace_emoji_shortcode(true);
 9530
 9531        editor.handle_input("Hello ", window, cx);
 9532        editor.handle_input(":wave", window, cx);
 9533        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9534
 9535        editor.handle_input(":", window, cx);
 9536        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9537
 9538        editor.handle_input(" :smile", window, cx);
 9539        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9540
 9541        editor.handle_input(":", window, cx);
 9542        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9543
 9544        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9545        editor.handle_input(":wave", window, cx);
 9546        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9547
 9548        editor.handle_input(":", window, cx);
 9549        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9550
 9551        editor.handle_input(":1", window, cx);
 9552        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9553
 9554        editor.handle_input(":", window, cx);
 9555        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9556
 9557        // Ensure shortcode does not get replaced when it is part of a word
 9558        editor.handle_input(" Test:wave", window, cx);
 9559        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9560
 9561        editor.handle_input(":", window, cx);
 9562        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9563
 9564        editor.set_auto_replace_emoji_shortcode(false);
 9565
 9566        // Ensure shortcode does not get replaced when auto replace is off
 9567        editor.handle_input(" :wave", window, cx);
 9568        assert_eq!(
 9569            editor.text(cx),
 9570            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9571        );
 9572
 9573        editor.handle_input(":", window, cx);
 9574        assert_eq!(
 9575            editor.text(cx),
 9576            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9577        );
 9578    });
 9579}
 9580
 9581#[gpui::test]
 9582async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9583    init_test(cx, |_| {});
 9584
 9585    let (text, insertion_ranges) = marked_text_ranges(
 9586        indoc! {"
 9587            ˇ
 9588        "},
 9589        false,
 9590    );
 9591
 9592    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9593    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9594
 9595    _ = editor.update_in(cx, |editor, window, cx| {
 9596        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9597
 9598        editor
 9599            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9600            .unwrap();
 9601
 9602        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9603            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9604            assert_eq!(editor.text(cx), expected_text);
 9605            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9606        }
 9607
 9608        assert(
 9609            editor,
 9610            cx,
 9611            indoc! {"
 9612            type «» =•
 9613            "},
 9614        );
 9615
 9616        assert!(editor.context_menu_visible(), "There should be a matches");
 9617    });
 9618}
 9619
 9620#[gpui::test]
 9621async fn test_snippets(cx: &mut TestAppContext) {
 9622    init_test(cx, |_| {});
 9623
 9624    let mut cx = EditorTestContext::new(cx).await;
 9625
 9626    cx.set_state(indoc! {"
 9627        a.ˇ b
 9628        a.ˇ b
 9629        a.ˇ b
 9630    "});
 9631
 9632    cx.update_editor(|editor, window, cx| {
 9633        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9634        let insertion_ranges = editor
 9635            .selections
 9636            .all(cx)
 9637            .iter()
 9638            .map(|s| s.range())
 9639            .collect::<Vec<_>>();
 9640        editor
 9641            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9642            .unwrap();
 9643    });
 9644
 9645    cx.assert_editor_state(indoc! {"
 9646        a.f(«oneˇ», two, «threeˇ») b
 9647        a.f(«oneˇ», two, «threeˇ») b
 9648        a.f(«oneˇ», two, «threeˇ») b
 9649    "});
 9650
 9651    // Can't move earlier than the first tab stop
 9652    cx.update_editor(|editor, window, cx| {
 9653        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9654    });
 9655    cx.assert_editor_state(indoc! {"
 9656        a.f(«oneˇ», two, «threeˇ») b
 9657        a.f(«oneˇ», two, «threeˇ») b
 9658        a.f(«oneˇ», two, «threeˇ») b
 9659    "});
 9660
 9661    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9662    cx.assert_editor_state(indoc! {"
 9663        a.f(one, «twoˇ», three) b
 9664        a.f(one, «twoˇ», three) b
 9665        a.f(one, «twoˇ», three) b
 9666    "});
 9667
 9668    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9669    cx.assert_editor_state(indoc! {"
 9670        a.f(«oneˇ», two, «threeˇ») b
 9671        a.f(«oneˇ», two, «threeˇ») b
 9672        a.f(«oneˇ», two, «threeˇ») b
 9673    "});
 9674
 9675    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9676    cx.assert_editor_state(indoc! {"
 9677        a.f(one, «twoˇ», three) b
 9678        a.f(one, «twoˇ», three) b
 9679        a.f(one, «twoˇ», three) b
 9680    "});
 9681    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9682    cx.assert_editor_state(indoc! {"
 9683        a.f(one, two, three)ˇ b
 9684        a.f(one, two, three)ˇ b
 9685        a.f(one, two, three)ˇ b
 9686    "});
 9687
 9688    // As soon as the last tab stop is reached, snippet state is gone
 9689    cx.update_editor(|editor, window, cx| {
 9690        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9691    });
 9692    cx.assert_editor_state(indoc! {"
 9693        a.f(one, two, three)ˇ b
 9694        a.f(one, two, three)ˇ b
 9695        a.f(one, two, three)ˇ b
 9696    "});
 9697}
 9698
 9699#[gpui::test]
 9700async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9701    init_test(cx, |_| {});
 9702
 9703    let mut cx = EditorTestContext::new(cx).await;
 9704
 9705    cx.update_editor(|editor, window, cx| {
 9706        let snippet = Snippet::parse(indoc! {"
 9707            /*
 9708             * Multiline comment with leading indentation
 9709             *
 9710             * $1
 9711             */
 9712            $0"})
 9713        .unwrap();
 9714        let insertion_ranges = editor
 9715            .selections
 9716            .all(cx)
 9717            .iter()
 9718            .map(|s| s.range())
 9719            .collect::<Vec<_>>();
 9720        editor
 9721            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9722            .unwrap();
 9723    });
 9724
 9725    cx.assert_editor_state(indoc! {"
 9726        /*
 9727         * Multiline comment with leading indentation
 9728         *
 9729         * ˇ
 9730         */
 9731    "});
 9732
 9733    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9734    cx.assert_editor_state(indoc! {"
 9735        /*
 9736         * Multiline comment with leading indentation
 9737         *
 9738         *•
 9739         */
 9740        ˇ"});
 9741}
 9742
 9743#[gpui::test]
 9744async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9745    init_test(cx, |_| {});
 9746
 9747    let fs = FakeFs::new(cx.executor());
 9748    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9749
 9750    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9751
 9752    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9753    language_registry.add(rust_lang());
 9754    let mut fake_servers = language_registry.register_fake_lsp(
 9755        "Rust",
 9756        FakeLspAdapter {
 9757            capabilities: lsp::ServerCapabilities {
 9758                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9759                ..Default::default()
 9760            },
 9761            ..Default::default()
 9762        },
 9763    );
 9764
 9765    let buffer = project
 9766        .update(cx, |project, cx| {
 9767            project.open_local_buffer(path!("/file.rs"), cx)
 9768        })
 9769        .await
 9770        .unwrap();
 9771
 9772    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9773    let (editor, cx) = cx.add_window_view(|window, cx| {
 9774        build_editor_with_project(project.clone(), buffer, window, cx)
 9775    });
 9776    editor.update_in(cx, |editor, window, cx| {
 9777        editor.set_text("one\ntwo\nthree\n", window, cx)
 9778    });
 9779    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9780
 9781    cx.executor().start_waiting();
 9782    let fake_server = fake_servers.next().await.unwrap();
 9783
 9784    {
 9785        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9786            move |params, _| async move {
 9787                assert_eq!(
 9788                    params.text_document.uri,
 9789                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9790                );
 9791                assert_eq!(params.options.tab_size, 4);
 9792                Ok(Some(vec![lsp::TextEdit::new(
 9793                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9794                    ", ".to_string(),
 9795                )]))
 9796            },
 9797        );
 9798        let save = editor
 9799            .update_in(cx, |editor, window, cx| {
 9800                editor.save(
 9801                    SaveOptions {
 9802                        format: true,
 9803                        autosave: false,
 9804                    },
 9805                    project.clone(),
 9806                    window,
 9807                    cx,
 9808                )
 9809            })
 9810            .unwrap();
 9811        cx.executor().start_waiting();
 9812        save.await;
 9813
 9814        assert_eq!(
 9815            editor.update(cx, |editor, cx| editor.text(cx)),
 9816            "one, two\nthree\n"
 9817        );
 9818        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9819    }
 9820
 9821    {
 9822        editor.update_in(cx, |editor, window, cx| {
 9823            editor.set_text("one\ntwo\nthree\n", window, cx)
 9824        });
 9825        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9826
 9827        // Ensure we can still save even if formatting hangs.
 9828        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9829            move |params, _| async move {
 9830                assert_eq!(
 9831                    params.text_document.uri,
 9832                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9833                );
 9834                futures::future::pending::<()>().await;
 9835                unreachable!()
 9836            },
 9837        );
 9838        let save = editor
 9839            .update_in(cx, |editor, window, cx| {
 9840                editor.save(
 9841                    SaveOptions {
 9842                        format: true,
 9843                        autosave: false,
 9844                    },
 9845                    project.clone(),
 9846                    window,
 9847                    cx,
 9848                )
 9849            })
 9850            .unwrap();
 9851        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9852        cx.executor().start_waiting();
 9853        save.await;
 9854        assert_eq!(
 9855            editor.update(cx, |editor, cx| editor.text(cx)),
 9856            "one\ntwo\nthree\n"
 9857        );
 9858    }
 9859
 9860    // Set rust language override and assert overridden tabsize is sent to language server
 9861    update_test_language_settings(cx, |settings| {
 9862        settings.languages.0.insert(
 9863            "Rust".into(),
 9864            LanguageSettingsContent {
 9865                tab_size: NonZeroU32::new(8),
 9866                ..Default::default()
 9867            },
 9868        );
 9869    });
 9870
 9871    {
 9872        editor.update_in(cx, |editor, window, cx| {
 9873            editor.set_text("somehting_new\n", window, cx)
 9874        });
 9875        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9876        let _formatting_request_signal = fake_server
 9877            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9878                assert_eq!(
 9879                    params.text_document.uri,
 9880                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9881                );
 9882                assert_eq!(params.options.tab_size, 8);
 9883                Ok(Some(vec![]))
 9884            });
 9885        let save = editor
 9886            .update_in(cx, |editor, window, cx| {
 9887                editor.save(
 9888                    SaveOptions {
 9889                        format: true,
 9890                        autosave: false,
 9891                    },
 9892                    project.clone(),
 9893                    window,
 9894                    cx,
 9895                )
 9896            })
 9897            .unwrap();
 9898        cx.executor().start_waiting();
 9899        save.await;
 9900    }
 9901}
 9902
 9903#[gpui::test]
 9904async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9905    init_test(cx, |settings| {
 9906        settings.defaults.ensure_final_newline_on_save = Some(false);
 9907    });
 9908
 9909    let fs = FakeFs::new(cx.executor());
 9910    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9911
 9912    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9913
 9914    let buffer = project
 9915        .update(cx, |project, cx| {
 9916            project.open_local_buffer(path!("/file.txt"), cx)
 9917        })
 9918        .await
 9919        .unwrap();
 9920
 9921    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9922    let (editor, cx) = cx.add_window_view(|window, cx| {
 9923        build_editor_with_project(project.clone(), buffer, window, cx)
 9924    });
 9925    editor.update_in(cx, |editor, window, cx| {
 9926        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9927            s.select_ranges([0..0])
 9928        });
 9929    });
 9930    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9931
 9932    editor.update_in(cx, |editor, window, cx| {
 9933        editor.handle_input("\n", window, cx)
 9934    });
 9935    cx.run_until_parked();
 9936    save(&editor, &project, cx).await;
 9937    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9938
 9939    editor.update_in(cx, |editor, window, cx| {
 9940        editor.undo(&Default::default(), window, cx);
 9941    });
 9942    save(&editor, &project, cx).await;
 9943    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9944
 9945    editor.update_in(cx, |editor, window, cx| {
 9946        editor.redo(&Default::default(), window, cx);
 9947    });
 9948    cx.run_until_parked();
 9949    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9950
 9951    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9952        let save = editor
 9953            .update_in(cx, |editor, window, cx| {
 9954                editor.save(
 9955                    SaveOptions {
 9956                        format: true,
 9957                        autosave: false,
 9958                    },
 9959                    project.clone(),
 9960                    window,
 9961                    cx,
 9962                )
 9963            })
 9964            .unwrap();
 9965        cx.executor().start_waiting();
 9966        save.await;
 9967        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9968    }
 9969}
 9970
 9971#[gpui::test]
 9972async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9973    init_test(cx, |_| {});
 9974
 9975    let cols = 4;
 9976    let rows = 10;
 9977    let sample_text_1 = sample_text(rows, cols, 'a');
 9978    assert_eq!(
 9979        sample_text_1,
 9980        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9981    );
 9982    let sample_text_2 = sample_text(rows, cols, 'l');
 9983    assert_eq!(
 9984        sample_text_2,
 9985        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9986    );
 9987    let sample_text_3 = sample_text(rows, cols, 'v');
 9988    assert_eq!(
 9989        sample_text_3,
 9990        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9991    );
 9992
 9993    let fs = FakeFs::new(cx.executor());
 9994    fs.insert_tree(
 9995        path!("/a"),
 9996        json!({
 9997            "main.rs": sample_text_1,
 9998            "other.rs": sample_text_2,
 9999            "lib.rs": sample_text_3,
10000        }),
10001    )
10002    .await;
10003
10004    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10005    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10006    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10007
10008    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10009    language_registry.add(rust_lang());
10010    let mut fake_servers = language_registry.register_fake_lsp(
10011        "Rust",
10012        FakeLspAdapter {
10013            capabilities: lsp::ServerCapabilities {
10014                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10015                ..Default::default()
10016            },
10017            ..Default::default()
10018        },
10019    );
10020
10021    let worktree = project.update(cx, |project, cx| {
10022        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10023        assert_eq!(worktrees.len(), 1);
10024        worktrees.pop().unwrap()
10025    });
10026    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10027
10028    let buffer_1 = project
10029        .update(cx, |project, cx| {
10030            project.open_buffer((worktree_id, "main.rs"), cx)
10031        })
10032        .await
10033        .unwrap();
10034    let buffer_2 = project
10035        .update(cx, |project, cx| {
10036            project.open_buffer((worktree_id, "other.rs"), cx)
10037        })
10038        .await
10039        .unwrap();
10040    let buffer_3 = project
10041        .update(cx, |project, cx| {
10042            project.open_buffer((worktree_id, "lib.rs"), cx)
10043        })
10044        .await
10045        .unwrap();
10046
10047    let multi_buffer = cx.new(|cx| {
10048        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10049        multi_buffer.push_excerpts(
10050            buffer_1.clone(),
10051            [
10052                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10053                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10054                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10055            ],
10056            cx,
10057        );
10058        multi_buffer.push_excerpts(
10059            buffer_2.clone(),
10060            [
10061                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10062                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10063                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10064            ],
10065            cx,
10066        );
10067        multi_buffer.push_excerpts(
10068            buffer_3.clone(),
10069            [
10070                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10071                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10072                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10073            ],
10074            cx,
10075        );
10076        multi_buffer
10077    });
10078    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10079        Editor::new(
10080            EditorMode::full(),
10081            multi_buffer,
10082            Some(project.clone()),
10083            window,
10084            cx,
10085        )
10086    });
10087
10088    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10089        editor.change_selections(
10090            SelectionEffects::scroll(Autoscroll::Next),
10091            window,
10092            cx,
10093            |s| s.select_ranges(Some(1..2)),
10094        );
10095        editor.insert("|one|two|three|", window, cx);
10096    });
10097    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10098    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10099        editor.change_selections(
10100            SelectionEffects::scroll(Autoscroll::Next),
10101            window,
10102            cx,
10103            |s| s.select_ranges(Some(60..70)),
10104        );
10105        editor.insert("|four|five|six|", window, cx);
10106    });
10107    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10108
10109    // First two buffers should be edited, but not the third one.
10110    assert_eq!(
10111        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10112        "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}",
10113    );
10114    buffer_1.update(cx, |buffer, _| {
10115        assert!(buffer.is_dirty());
10116        assert_eq!(
10117            buffer.text(),
10118            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10119        )
10120    });
10121    buffer_2.update(cx, |buffer, _| {
10122        assert!(buffer.is_dirty());
10123        assert_eq!(
10124            buffer.text(),
10125            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10126        )
10127    });
10128    buffer_3.update(cx, |buffer, _| {
10129        assert!(!buffer.is_dirty());
10130        assert_eq!(buffer.text(), sample_text_3,)
10131    });
10132    cx.executor().run_until_parked();
10133
10134    cx.executor().start_waiting();
10135    let save = multi_buffer_editor
10136        .update_in(cx, |editor, window, cx| {
10137            editor.save(
10138                SaveOptions {
10139                    format: true,
10140                    autosave: false,
10141                },
10142                project.clone(),
10143                window,
10144                cx,
10145            )
10146        })
10147        .unwrap();
10148
10149    let fake_server = fake_servers.next().await.unwrap();
10150    fake_server
10151        .server
10152        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10153            Ok(Some(vec![lsp::TextEdit::new(
10154                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10155                format!("[{} formatted]", params.text_document.uri),
10156            )]))
10157        })
10158        .detach();
10159    save.await;
10160
10161    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10162    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10163    assert_eq!(
10164        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10165        uri!(
10166            "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}"
10167        ),
10168    );
10169    buffer_1.update(cx, |buffer, _| {
10170        assert!(!buffer.is_dirty());
10171        assert_eq!(
10172            buffer.text(),
10173            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10174        )
10175    });
10176    buffer_2.update(cx, |buffer, _| {
10177        assert!(!buffer.is_dirty());
10178        assert_eq!(
10179            buffer.text(),
10180            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10181        )
10182    });
10183    buffer_3.update(cx, |buffer, _| {
10184        assert!(!buffer.is_dirty());
10185        assert_eq!(buffer.text(), sample_text_3,)
10186    });
10187}
10188
10189#[gpui::test]
10190async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10191    init_test(cx, |_| {});
10192
10193    let fs = FakeFs::new(cx.executor());
10194    fs.insert_tree(
10195        path!("/dir"),
10196        json!({
10197            "file1.rs": "fn main() { println!(\"hello\"); }",
10198            "file2.rs": "fn test() { println!(\"test\"); }",
10199            "file3.rs": "fn other() { println!(\"other\"); }\n",
10200        }),
10201    )
10202    .await;
10203
10204    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10205    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10206    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10207
10208    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10209    language_registry.add(rust_lang());
10210
10211    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10212    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10213
10214    // Open three buffers
10215    let buffer_1 = project
10216        .update(cx, |project, cx| {
10217            project.open_buffer((worktree_id, "file1.rs"), cx)
10218        })
10219        .await
10220        .unwrap();
10221    let buffer_2 = project
10222        .update(cx, |project, cx| {
10223            project.open_buffer((worktree_id, "file2.rs"), cx)
10224        })
10225        .await
10226        .unwrap();
10227    let buffer_3 = project
10228        .update(cx, |project, cx| {
10229            project.open_buffer((worktree_id, "file3.rs"), cx)
10230        })
10231        .await
10232        .unwrap();
10233
10234    // Create a multi-buffer with all three buffers
10235    let multi_buffer = cx.new(|cx| {
10236        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10237        multi_buffer.push_excerpts(
10238            buffer_1.clone(),
10239            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10240            cx,
10241        );
10242        multi_buffer.push_excerpts(
10243            buffer_2.clone(),
10244            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10245            cx,
10246        );
10247        multi_buffer.push_excerpts(
10248            buffer_3.clone(),
10249            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10250            cx,
10251        );
10252        multi_buffer
10253    });
10254
10255    let editor = cx.new_window_entity(|window, cx| {
10256        Editor::new(
10257            EditorMode::full(),
10258            multi_buffer,
10259            Some(project.clone()),
10260            window,
10261            cx,
10262        )
10263    });
10264
10265    // Edit only the first buffer
10266    editor.update_in(cx, |editor, window, cx| {
10267        editor.change_selections(
10268            SelectionEffects::scroll(Autoscroll::Next),
10269            window,
10270            cx,
10271            |s| s.select_ranges(Some(10..10)),
10272        );
10273        editor.insert("// edited", window, cx);
10274    });
10275
10276    // Verify that only buffer 1 is dirty
10277    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10278    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10279    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10280
10281    // Get write counts after file creation (files were created with initial content)
10282    // We expect each file to have been written once during creation
10283    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10284    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10285    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10286
10287    // Perform autosave
10288    let save_task = editor.update_in(cx, |editor, window, cx| {
10289        editor.save(
10290            SaveOptions {
10291                format: true,
10292                autosave: true,
10293            },
10294            project.clone(),
10295            window,
10296            cx,
10297        )
10298    });
10299    save_task.await.unwrap();
10300
10301    // Only the dirty buffer should have been saved
10302    assert_eq!(
10303        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10304        1,
10305        "Buffer 1 was dirty, so it should have been written once during autosave"
10306    );
10307    assert_eq!(
10308        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10309        0,
10310        "Buffer 2 was clean, so it should not have been written during autosave"
10311    );
10312    assert_eq!(
10313        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10314        0,
10315        "Buffer 3 was clean, so it should not have been written during autosave"
10316    );
10317
10318    // Verify buffer states after autosave
10319    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10320    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10321    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10322
10323    // Now perform a manual save (format = true)
10324    let save_task = editor.update_in(cx, |editor, window, cx| {
10325        editor.save(
10326            SaveOptions {
10327                format: true,
10328                autosave: false,
10329            },
10330            project.clone(),
10331            window,
10332            cx,
10333        )
10334    });
10335    save_task.await.unwrap();
10336
10337    // During manual save, clean buffers don't get written to disk
10338    // They just get did_save called for language server notifications
10339    assert_eq!(
10340        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10341        1,
10342        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10343    );
10344    assert_eq!(
10345        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10346        0,
10347        "Buffer 2 should not have been written at all"
10348    );
10349    assert_eq!(
10350        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10351        0,
10352        "Buffer 3 should not have been written at all"
10353    );
10354}
10355
10356async fn setup_range_format_test(
10357    cx: &mut TestAppContext,
10358) -> (
10359    Entity<Project>,
10360    Entity<Editor>,
10361    &mut gpui::VisualTestContext,
10362    lsp::FakeLanguageServer,
10363) {
10364    init_test(cx, |_| {});
10365
10366    let fs = FakeFs::new(cx.executor());
10367    fs.insert_file(path!("/file.rs"), Default::default()).await;
10368
10369    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10370
10371    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10372    language_registry.add(rust_lang());
10373    let mut fake_servers = language_registry.register_fake_lsp(
10374        "Rust",
10375        FakeLspAdapter {
10376            capabilities: lsp::ServerCapabilities {
10377                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10378                ..lsp::ServerCapabilities::default()
10379            },
10380            ..FakeLspAdapter::default()
10381        },
10382    );
10383
10384    let buffer = project
10385        .update(cx, |project, cx| {
10386            project.open_local_buffer(path!("/file.rs"), cx)
10387        })
10388        .await
10389        .unwrap();
10390
10391    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10392    let (editor, cx) = cx.add_window_view(|window, cx| {
10393        build_editor_with_project(project.clone(), buffer, window, cx)
10394    });
10395
10396    cx.executor().start_waiting();
10397    let fake_server = fake_servers.next().await.unwrap();
10398
10399    (project, editor, cx, fake_server)
10400}
10401
10402#[gpui::test]
10403async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10404    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10405
10406    editor.update_in(cx, |editor, window, cx| {
10407        editor.set_text("one\ntwo\nthree\n", window, cx)
10408    });
10409    assert!(cx.read(|cx| editor.is_dirty(cx)));
10410
10411    let save = editor
10412        .update_in(cx, |editor, window, cx| {
10413            editor.save(
10414                SaveOptions {
10415                    format: true,
10416                    autosave: false,
10417                },
10418                project.clone(),
10419                window,
10420                cx,
10421            )
10422        })
10423        .unwrap();
10424    fake_server
10425        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10426            assert_eq!(
10427                params.text_document.uri,
10428                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10429            );
10430            assert_eq!(params.options.tab_size, 4);
10431            Ok(Some(vec![lsp::TextEdit::new(
10432                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10433                ", ".to_string(),
10434            )]))
10435        })
10436        .next()
10437        .await;
10438    cx.executor().start_waiting();
10439    save.await;
10440    assert_eq!(
10441        editor.update(cx, |editor, cx| editor.text(cx)),
10442        "one, two\nthree\n"
10443    );
10444    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10445}
10446
10447#[gpui::test]
10448async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10449    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10450
10451    editor.update_in(cx, |editor, window, cx| {
10452        editor.set_text("one\ntwo\nthree\n", window, cx)
10453    });
10454    assert!(cx.read(|cx| editor.is_dirty(cx)));
10455
10456    // Test that save still works when formatting hangs
10457    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10458        move |params, _| async move {
10459            assert_eq!(
10460                params.text_document.uri,
10461                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10462            );
10463            futures::future::pending::<()>().await;
10464            unreachable!()
10465        },
10466    );
10467    let save = editor
10468        .update_in(cx, |editor, window, cx| {
10469            editor.save(
10470                SaveOptions {
10471                    format: true,
10472                    autosave: false,
10473                },
10474                project.clone(),
10475                window,
10476                cx,
10477            )
10478        })
10479        .unwrap();
10480    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10481    cx.executor().start_waiting();
10482    save.await;
10483    assert_eq!(
10484        editor.update(cx, |editor, cx| editor.text(cx)),
10485        "one\ntwo\nthree\n"
10486    );
10487    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10488}
10489
10490#[gpui::test]
10491async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10492    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10493
10494    // Buffer starts clean, no formatting should be requested
10495    let save = editor
10496        .update_in(cx, |editor, window, cx| {
10497            editor.save(
10498                SaveOptions {
10499                    format: false,
10500                    autosave: false,
10501                },
10502                project.clone(),
10503                window,
10504                cx,
10505            )
10506        })
10507        .unwrap();
10508    let _pending_format_request = fake_server
10509        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10510            panic!("Should not be invoked");
10511        })
10512        .next();
10513    cx.executor().start_waiting();
10514    save.await;
10515    cx.run_until_parked();
10516}
10517
10518#[gpui::test]
10519async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10520    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10521
10522    // Set Rust language override and assert overridden tabsize is sent to language server
10523    update_test_language_settings(cx, |settings| {
10524        settings.languages.0.insert(
10525            "Rust".into(),
10526            LanguageSettingsContent {
10527                tab_size: NonZeroU32::new(8),
10528                ..Default::default()
10529            },
10530        );
10531    });
10532
10533    editor.update_in(cx, |editor, window, cx| {
10534        editor.set_text("something_new\n", window, cx)
10535    });
10536    assert!(cx.read(|cx| editor.is_dirty(cx)));
10537    let save = editor
10538        .update_in(cx, |editor, window, cx| {
10539            editor.save(
10540                SaveOptions {
10541                    format: true,
10542                    autosave: false,
10543                },
10544                project.clone(),
10545                window,
10546                cx,
10547            )
10548        })
10549        .unwrap();
10550    fake_server
10551        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10552            assert_eq!(
10553                params.text_document.uri,
10554                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10555            );
10556            assert_eq!(params.options.tab_size, 8);
10557            Ok(Some(Vec::new()))
10558        })
10559        .next()
10560        .await;
10561    save.await;
10562}
10563
10564#[gpui::test]
10565async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10566    init_test(cx, |settings| {
10567        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10568            Formatter::LanguageServer { name: None },
10569        )))
10570    });
10571
10572    let fs = FakeFs::new(cx.executor());
10573    fs.insert_file(path!("/file.rs"), Default::default()).await;
10574
10575    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10576
10577    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10578    language_registry.add(Arc::new(Language::new(
10579        LanguageConfig {
10580            name: "Rust".into(),
10581            matcher: LanguageMatcher {
10582                path_suffixes: vec!["rs".to_string()],
10583                ..Default::default()
10584            },
10585            ..LanguageConfig::default()
10586        },
10587        Some(tree_sitter_rust::LANGUAGE.into()),
10588    )));
10589    update_test_language_settings(cx, |settings| {
10590        // Enable Prettier formatting for the same buffer, and ensure
10591        // LSP is called instead of Prettier.
10592        settings.defaults.prettier = Some(PrettierSettings {
10593            allowed: true,
10594            ..PrettierSettings::default()
10595        });
10596    });
10597    let mut fake_servers = language_registry.register_fake_lsp(
10598        "Rust",
10599        FakeLspAdapter {
10600            capabilities: lsp::ServerCapabilities {
10601                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10602                ..Default::default()
10603            },
10604            ..Default::default()
10605        },
10606    );
10607
10608    let buffer = project
10609        .update(cx, |project, cx| {
10610            project.open_local_buffer(path!("/file.rs"), cx)
10611        })
10612        .await
10613        .unwrap();
10614
10615    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10616    let (editor, cx) = cx.add_window_view(|window, cx| {
10617        build_editor_with_project(project.clone(), buffer, window, cx)
10618    });
10619    editor.update_in(cx, |editor, window, cx| {
10620        editor.set_text("one\ntwo\nthree\n", window, cx)
10621    });
10622
10623    cx.executor().start_waiting();
10624    let fake_server = fake_servers.next().await.unwrap();
10625
10626    let format = editor
10627        .update_in(cx, |editor, window, cx| {
10628            editor.perform_format(
10629                project.clone(),
10630                FormatTrigger::Manual,
10631                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10632                window,
10633                cx,
10634            )
10635        })
10636        .unwrap();
10637    fake_server
10638        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10639            assert_eq!(
10640                params.text_document.uri,
10641                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10642            );
10643            assert_eq!(params.options.tab_size, 4);
10644            Ok(Some(vec![lsp::TextEdit::new(
10645                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10646                ", ".to_string(),
10647            )]))
10648        })
10649        .next()
10650        .await;
10651    cx.executor().start_waiting();
10652    format.await;
10653    assert_eq!(
10654        editor.update(cx, |editor, cx| editor.text(cx)),
10655        "one, two\nthree\n"
10656    );
10657
10658    editor.update_in(cx, |editor, window, cx| {
10659        editor.set_text("one\ntwo\nthree\n", window, cx)
10660    });
10661    // Ensure we don't lock if formatting hangs.
10662    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10663        move |params, _| async move {
10664            assert_eq!(
10665                params.text_document.uri,
10666                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10667            );
10668            futures::future::pending::<()>().await;
10669            unreachable!()
10670        },
10671    );
10672    let format = editor
10673        .update_in(cx, |editor, window, cx| {
10674            editor.perform_format(
10675                project,
10676                FormatTrigger::Manual,
10677                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10678                window,
10679                cx,
10680            )
10681        })
10682        .unwrap();
10683    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10684    cx.executor().start_waiting();
10685    format.await;
10686    assert_eq!(
10687        editor.update(cx, |editor, cx| editor.text(cx)),
10688        "one\ntwo\nthree\n"
10689    );
10690}
10691
10692#[gpui::test]
10693async fn test_multiple_formatters(cx: &mut TestAppContext) {
10694    init_test(cx, |settings| {
10695        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10696        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10697            Formatter::LanguageServer { name: None },
10698            Formatter::CodeActions(
10699                [
10700                    ("code-action-1".into(), true),
10701                    ("code-action-2".into(), true),
10702                ]
10703                .into_iter()
10704                .collect(),
10705            ),
10706        ])))
10707    });
10708
10709    let fs = FakeFs::new(cx.executor());
10710    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10711        .await;
10712
10713    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10714    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10715    language_registry.add(rust_lang());
10716
10717    let mut fake_servers = language_registry.register_fake_lsp(
10718        "Rust",
10719        FakeLspAdapter {
10720            capabilities: lsp::ServerCapabilities {
10721                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10722                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10723                    commands: vec!["the-command-for-code-action-1".into()],
10724                    ..Default::default()
10725                }),
10726                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10727                ..Default::default()
10728            },
10729            ..Default::default()
10730        },
10731    );
10732
10733    let buffer = project
10734        .update(cx, |project, cx| {
10735            project.open_local_buffer(path!("/file.rs"), cx)
10736        })
10737        .await
10738        .unwrap();
10739
10740    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10741    let (editor, cx) = cx.add_window_view(|window, cx| {
10742        build_editor_with_project(project.clone(), buffer, window, cx)
10743    });
10744
10745    cx.executor().start_waiting();
10746
10747    let fake_server = fake_servers.next().await.unwrap();
10748    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10749        move |_params, _| async move {
10750            Ok(Some(vec![lsp::TextEdit::new(
10751                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10752                "applied-formatting\n".to_string(),
10753            )]))
10754        },
10755    );
10756    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10757        move |params, _| async move {
10758            assert_eq!(
10759                params.context.only,
10760                Some(vec!["code-action-1".into(), "code-action-2".into()])
10761            );
10762            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10763            Ok(Some(vec![
10764                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10765                    kind: Some("code-action-1".into()),
10766                    edit: Some(lsp::WorkspaceEdit::new(
10767                        [(
10768                            uri.clone(),
10769                            vec![lsp::TextEdit::new(
10770                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10771                                "applied-code-action-1-edit\n".to_string(),
10772                            )],
10773                        )]
10774                        .into_iter()
10775                        .collect(),
10776                    )),
10777                    command: Some(lsp::Command {
10778                        command: "the-command-for-code-action-1".into(),
10779                        ..Default::default()
10780                    }),
10781                    ..Default::default()
10782                }),
10783                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10784                    kind: Some("code-action-2".into()),
10785                    edit: Some(lsp::WorkspaceEdit::new(
10786                        [(
10787                            uri,
10788                            vec![lsp::TextEdit::new(
10789                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10790                                "applied-code-action-2-edit\n".to_string(),
10791                            )],
10792                        )]
10793                        .into_iter()
10794                        .collect(),
10795                    )),
10796                    ..Default::default()
10797                }),
10798            ]))
10799        },
10800    );
10801
10802    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10803        move |params, _| async move { Ok(params) }
10804    });
10805
10806    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10807    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10808        let fake = fake_server.clone();
10809        let lock = command_lock.clone();
10810        move |params, _| {
10811            assert_eq!(params.command, "the-command-for-code-action-1");
10812            let fake = fake.clone();
10813            let lock = lock.clone();
10814            async move {
10815                lock.lock().await;
10816                fake.server
10817                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10818                        label: None,
10819                        edit: lsp::WorkspaceEdit {
10820                            changes: Some(
10821                                [(
10822                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10823                                    vec![lsp::TextEdit {
10824                                        range: lsp::Range::new(
10825                                            lsp::Position::new(0, 0),
10826                                            lsp::Position::new(0, 0),
10827                                        ),
10828                                        new_text: "applied-code-action-1-command\n".into(),
10829                                    }],
10830                                )]
10831                                .into_iter()
10832                                .collect(),
10833                            ),
10834                            ..Default::default()
10835                        },
10836                    })
10837                    .await
10838                    .into_response()
10839                    .unwrap();
10840                Ok(Some(json!(null)))
10841            }
10842        }
10843    });
10844
10845    cx.executor().start_waiting();
10846    editor
10847        .update_in(cx, |editor, window, cx| {
10848            editor.perform_format(
10849                project.clone(),
10850                FormatTrigger::Manual,
10851                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10852                window,
10853                cx,
10854            )
10855        })
10856        .unwrap()
10857        .await;
10858    editor.update(cx, |editor, cx| {
10859        assert_eq!(
10860            editor.text(cx),
10861            r#"
10862                applied-code-action-2-edit
10863                applied-code-action-1-command
10864                applied-code-action-1-edit
10865                applied-formatting
10866                one
10867                two
10868                three
10869            "#
10870            .unindent()
10871        );
10872    });
10873
10874    editor.update_in(cx, |editor, window, cx| {
10875        editor.undo(&Default::default(), window, cx);
10876        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10877    });
10878
10879    // Perform a manual edit while waiting for an LSP command
10880    // that's being run as part of a formatting code action.
10881    let lock_guard = command_lock.lock().await;
10882    let format = editor
10883        .update_in(cx, |editor, window, cx| {
10884            editor.perform_format(
10885                project.clone(),
10886                FormatTrigger::Manual,
10887                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10888                window,
10889                cx,
10890            )
10891        })
10892        .unwrap();
10893    cx.run_until_parked();
10894    editor.update(cx, |editor, cx| {
10895        assert_eq!(
10896            editor.text(cx),
10897            r#"
10898                applied-code-action-1-edit
10899                applied-formatting
10900                one
10901                two
10902                three
10903            "#
10904            .unindent()
10905        );
10906
10907        editor.buffer.update(cx, |buffer, cx| {
10908            let ix = buffer.len(cx);
10909            buffer.edit([(ix..ix, "edited\n")], None, cx);
10910        });
10911    });
10912
10913    // Allow the LSP command to proceed. Because the buffer was edited,
10914    // the second code action will not be run.
10915    drop(lock_guard);
10916    format.await;
10917    editor.update_in(cx, |editor, window, cx| {
10918        assert_eq!(
10919            editor.text(cx),
10920            r#"
10921                applied-code-action-1-command
10922                applied-code-action-1-edit
10923                applied-formatting
10924                one
10925                two
10926                three
10927                edited
10928            "#
10929            .unindent()
10930        );
10931
10932        // The manual edit is undone first, because it is the last thing the user did
10933        // (even though the command completed afterwards).
10934        editor.undo(&Default::default(), window, cx);
10935        assert_eq!(
10936            editor.text(cx),
10937            r#"
10938                applied-code-action-1-command
10939                applied-code-action-1-edit
10940                applied-formatting
10941                one
10942                two
10943                three
10944            "#
10945            .unindent()
10946        );
10947
10948        // All the formatting (including the command, which completed after the manual edit)
10949        // is undone together.
10950        editor.undo(&Default::default(), window, cx);
10951        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10952    });
10953}
10954
10955#[gpui::test]
10956async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10957    init_test(cx, |settings| {
10958        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10959            Formatter::LanguageServer { name: None },
10960        ])))
10961    });
10962
10963    let fs = FakeFs::new(cx.executor());
10964    fs.insert_file(path!("/file.ts"), Default::default()).await;
10965
10966    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10967
10968    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10969    language_registry.add(Arc::new(Language::new(
10970        LanguageConfig {
10971            name: "TypeScript".into(),
10972            matcher: LanguageMatcher {
10973                path_suffixes: vec!["ts".to_string()],
10974                ..Default::default()
10975            },
10976            ..LanguageConfig::default()
10977        },
10978        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10979    )));
10980    update_test_language_settings(cx, |settings| {
10981        settings.defaults.prettier = Some(PrettierSettings {
10982            allowed: true,
10983            ..PrettierSettings::default()
10984        });
10985    });
10986    let mut fake_servers = language_registry.register_fake_lsp(
10987        "TypeScript",
10988        FakeLspAdapter {
10989            capabilities: lsp::ServerCapabilities {
10990                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10991                ..Default::default()
10992            },
10993            ..Default::default()
10994        },
10995    );
10996
10997    let buffer = project
10998        .update(cx, |project, cx| {
10999            project.open_local_buffer(path!("/file.ts"), cx)
11000        })
11001        .await
11002        .unwrap();
11003
11004    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11005    let (editor, cx) = cx.add_window_view(|window, cx| {
11006        build_editor_with_project(project.clone(), buffer, window, cx)
11007    });
11008    editor.update_in(cx, |editor, window, cx| {
11009        editor.set_text(
11010            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11011            window,
11012            cx,
11013        )
11014    });
11015
11016    cx.executor().start_waiting();
11017    let fake_server = fake_servers.next().await.unwrap();
11018
11019    let format = editor
11020        .update_in(cx, |editor, window, cx| {
11021            editor.perform_code_action_kind(
11022                project.clone(),
11023                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11024                window,
11025                cx,
11026            )
11027        })
11028        .unwrap();
11029    fake_server
11030        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11031            assert_eq!(
11032                params.text_document.uri,
11033                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11034            );
11035            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11036                lsp::CodeAction {
11037                    title: "Organize Imports".to_string(),
11038                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11039                    edit: Some(lsp::WorkspaceEdit {
11040                        changes: Some(
11041                            [(
11042                                params.text_document.uri.clone(),
11043                                vec![lsp::TextEdit::new(
11044                                    lsp::Range::new(
11045                                        lsp::Position::new(1, 0),
11046                                        lsp::Position::new(2, 0),
11047                                    ),
11048                                    "".to_string(),
11049                                )],
11050                            )]
11051                            .into_iter()
11052                            .collect(),
11053                        ),
11054                        ..Default::default()
11055                    }),
11056                    ..Default::default()
11057                },
11058            )]))
11059        })
11060        .next()
11061        .await;
11062    cx.executor().start_waiting();
11063    format.await;
11064    assert_eq!(
11065        editor.update(cx, |editor, cx| editor.text(cx)),
11066        "import { a } from 'module';\n\nconst x = a;\n"
11067    );
11068
11069    editor.update_in(cx, |editor, window, cx| {
11070        editor.set_text(
11071            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11072            window,
11073            cx,
11074        )
11075    });
11076    // Ensure we don't lock if code action hangs.
11077    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11078        move |params, _| async move {
11079            assert_eq!(
11080                params.text_document.uri,
11081                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11082            );
11083            futures::future::pending::<()>().await;
11084            unreachable!()
11085        },
11086    );
11087    let format = editor
11088        .update_in(cx, |editor, window, cx| {
11089            editor.perform_code_action_kind(
11090                project,
11091                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11092                window,
11093                cx,
11094            )
11095        })
11096        .unwrap();
11097    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11098    cx.executor().start_waiting();
11099    format.await;
11100    assert_eq!(
11101        editor.update(cx, |editor, cx| editor.text(cx)),
11102        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11103    );
11104}
11105
11106#[gpui::test]
11107async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11108    init_test(cx, |_| {});
11109
11110    let mut cx = EditorLspTestContext::new_rust(
11111        lsp::ServerCapabilities {
11112            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11113            ..Default::default()
11114        },
11115        cx,
11116    )
11117    .await;
11118
11119    cx.set_state(indoc! {"
11120        one.twoˇ
11121    "});
11122
11123    // The format request takes a long time. When it completes, it inserts
11124    // a newline and an indent before the `.`
11125    cx.lsp
11126        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11127            let executor = cx.background_executor().clone();
11128            async move {
11129                executor.timer(Duration::from_millis(100)).await;
11130                Ok(Some(vec![lsp::TextEdit {
11131                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11132                    new_text: "\n    ".into(),
11133                }]))
11134            }
11135        });
11136
11137    // Submit a format request.
11138    let format_1 = cx
11139        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11140        .unwrap();
11141    cx.executor().run_until_parked();
11142
11143    // Submit a second format request.
11144    let format_2 = cx
11145        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11146        .unwrap();
11147    cx.executor().run_until_parked();
11148
11149    // Wait for both format requests to complete
11150    cx.executor().advance_clock(Duration::from_millis(200));
11151    cx.executor().start_waiting();
11152    format_1.await.unwrap();
11153    cx.executor().start_waiting();
11154    format_2.await.unwrap();
11155
11156    // The formatting edits only happens once.
11157    cx.assert_editor_state(indoc! {"
11158        one
11159            .twoˇ
11160    "});
11161}
11162
11163#[gpui::test]
11164async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11165    init_test(cx, |settings| {
11166        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11167    });
11168
11169    let mut cx = EditorLspTestContext::new_rust(
11170        lsp::ServerCapabilities {
11171            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11172            ..Default::default()
11173        },
11174        cx,
11175    )
11176    .await;
11177
11178    // Set up a buffer white some trailing whitespace and no trailing newline.
11179    cx.set_state(
11180        &[
11181            "one ",   //
11182            "twoˇ",   //
11183            "three ", //
11184            "four",   //
11185        ]
11186        .join("\n"),
11187    );
11188
11189    // Submit a format request.
11190    let format = cx
11191        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11192        .unwrap();
11193
11194    // Record which buffer changes have been sent to the language server
11195    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11196    cx.lsp
11197        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11198            let buffer_changes = buffer_changes.clone();
11199            move |params, _| {
11200                buffer_changes.lock().extend(
11201                    params
11202                        .content_changes
11203                        .into_iter()
11204                        .map(|e| (e.range.unwrap(), e.text)),
11205                );
11206            }
11207        });
11208
11209    // Handle formatting requests to the language server.
11210    cx.lsp
11211        .set_request_handler::<lsp::request::Formatting, _, _>({
11212            let buffer_changes = buffer_changes.clone();
11213            move |_, _| {
11214                // When formatting is requested, trailing whitespace has already been stripped,
11215                // and the trailing newline has already been added.
11216                assert_eq!(
11217                    &buffer_changes.lock()[1..],
11218                    &[
11219                        (
11220                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11221                            "".into()
11222                        ),
11223                        (
11224                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11225                            "".into()
11226                        ),
11227                        (
11228                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11229                            "\n".into()
11230                        ),
11231                    ]
11232                );
11233
11234                // Insert blank lines between each line of the buffer.
11235                async move {
11236                    Ok(Some(vec![
11237                        lsp::TextEdit {
11238                            range: lsp::Range::new(
11239                                lsp::Position::new(1, 0),
11240                                lsp::Position::new(1, 0),
11241                            ),
11242                            new_text: "\n".into(),
11243                        },
11244                        lsp::TextEdit {
11245                            range: lsp::Range::new(
11246                                lsp::Position::new(2, 0),
11247                                lsp::Position::new(2, 0),
11248                            ),
11249                            new_text: "\n".into(),
11250                        },
11251                    ]))
11252                }
11253            }
11254        });
11255
11256    // After formatting the buffer, the trailing whitespace is stripped,
11257    // a newline is appended, and the edits provided by the language server
11258    // have been applied.
11259    format.await.unwrap();
11260    cx.assert_editor_state(
11261        &[
11262            "one",   //
11263            "",      //
11264            "twoˇ",  //
11265            "",      //
11266            "three", //
11267            "four",  //
11268            "",      //
11269        ]
11270        .join("\n"),
11271    );
11272
11273    // Undoing the formatting undoes the trailing whitespace removal, the
11274    // trailing newline, and the LSP edits.
11275    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11276    cx.assert_editor_state(
11277        &[
11278            "one ",   //
11279            "twoˇ",   //
11280            "three ", //
11281            "four",   //
11282        ]
11283        .join("\n"),
11284    );
11285}
11286
11287#[gpui::test]
11288async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11289    cx: &mut TestAppContext,
11290) {
11291    init_test(cx, |_| {});
11292
11293    cx.update(|cx| {
11294        cx.update_global::<SettingsStore, _>(|settings, cx| {
11295            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11296                settings.auto_signature_help = Some(true);
11297            });
11298        });
11299    });
11300
11301    let mut cx = EditorLspTestContext::new_rust(
11302        lsp::ServerCapabilities {
11303            signature_help_provider: Some(lsp::SignatureHelpOptions {
11304                ..Default::default()
11305            }),
11306            ..Default::default()
11307        },
11308        cx,
11309    )
11310    .await;
11311
11312    let language = Language::new(
11313        LanguageConfig {
11314            name: "Rust".into(),
11315            brackets: BracketPairConfig {
11316                pairs: vec![
11317                    BracketPair {
11318                        start: "{".to_string(),
11319                        end: "}".to_string(),
11320                        close: true,
11321                        surround: true,
11322                        newline: true,
11323                    },
11324                    BracketPair {
11325                        start: "(".to_string(),
11326                        end: ")".to_string(),
11327                        close: true,
11328                        surround: true,
11329                        newline: true,
11330                    },
11331                    BracketPair {
11332                        start: "/*".to_string(),
11333                        end: " */".to_string(),
11334                        close: true,
11335                        surround: true,
11336                        newline: true,
11337                    },
11338                    BracketPair {
11339                        start: "[".to_string(),
11340                        end: "]".to_string(),
11341                        close: false,
11342                        surround: false,
11343                        newline: true,
11344                    },
11345                    BracketPair {
11346                        start: "\"".to_string(),
11347                        end: "\"".to_string(),
11348                        close: true,
11349                        surround: true,
11350                        newline: false,
11351                    },
11352                    BracketPair {
11353                        start: "<".to_string(),
11354                        end: ">".to_string(),
11355                        close: false,
11356                        surround: true,
11357                        newline: true,
11358                    },
11359                ],
11360                ..Default::default()
11361            },
11362            autoclose_before: "})]".to_string(),
11363            ..Default::default()
11364        },
11365        Some(tree_sitter_rust::LANGUAGE.into()),
11366    );
11367    let language = Arc::new(language);
11368
11369    cx.language_registry().add(language.clone());
11370    cx.update_buffer(|buffer, cx| {
11371        buffer.set_language(Some(language), cx);
11372    });
11373
11374    cx.set_state(
11375        &r#"
11376            fn main() {
11377                sampleˇ
11378            }
11379        "#
11380        .unindent(),
11381    );
11382
11383    cx.update_editor(|editor, window, cx| {
11384        editor.handle_input("(", window, cx);
11385    });
11386    cx.assert_editor_state(
11387        &"
11388            fn main() {
11389                sample(ˇ)
11390            }
11391        "
11392        .unindent(),
11393    );
11394
11395    let mocked_response = lsp::SignatureHelp {
11396        signatures: vec![lsp::SignatureInformation {
11397            label: "fn sample(param1: u8, param2: u8)".to_string(),
11398            documentation: None,
11399            parameters: Some(vec![
11400                lsp::ParameterInformation {
11401                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11402                    documentation: None,
11403                },
11404                lsp::ParameterInformation {
11405                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11406                    documentation: None,
11407                },
11408            ]),
11409            active_parameter: None,
11410        }],
11411        active_signature: Some(0),
11412        active_parameter: Some(0),
11413    };
11414    handle_signature_help_request(&mut cx, mocked_response).await;
11415
11416    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11417        .await;
11418
11419    cx.editor(|editor, _, _| {
11420        let signature_help_state = editor.signature_help_state.popover().cloned();
11421        let signature = signature_help_state.unwrap();
11422        assert_eq!(
11423            signature.signatures[signature.current_signature].label,
11424            "fn sample(param1: u8, param2: u8)"
11425        );
11426    });
11427}
11428
11429#[gpui::test]
11430async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11431    init_test(cx, |_| {});
11432
11433    cx.update(|cx| {
11434        cx.update_global::<SettingsStore, _>(|settings, cx| {
11435            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11436                settings.auto_signature_help = Some(false);
11437                settings.show_signature_help_after_edits = Some(false);
11438            });
11439        });
11440    });
11441
11442    let mut cx = EditorLspTestContext::new_rust(
11443        lsp::ServerCapabilities {
11444            signature_help_provider: Some(lsp::SignatureHelpOptions {
11445                ..Default::default()
11446            }),
11447            ..Default::default()
11448        },
11449        cx,
11450    )
11451    .await;
11452
11453    let language = Language::new(
11454        LanguageConfig {
11455            name: "Rust".into(),
11456            brackets: BracketPairConfig {
11457                pairs: vec![
11458                    BracketPair {
11459                        start: "{".to_string(),
11460                        end: "}".to_string(),
11461                        close: true,
11462                        surround: true,
11463                        newline: true,
11464                    },
11465                    BracketPair {
11466                        start: "(".to_string(),
11467                        end: ")".to_string(),
11468                        close: true,
11469                        surround: true,
11470                        newline: true,
11471                    },
11472                    BracketPair {
11473                        start: "/*".to_string(),
11474                        end: " */".to_string(),
11475                        close: true,
11476                        surround: true,
11477                        newline: true,
11478                    },
11479                    BracketPair {
11480                        start: "[".to_string(),
11481                        end: "]".to_string(),
11482                        close: false,
11483                        surround: false,
11484                        newline: true,
11485                    },
11486                    BracketPair {
11487                        start: "\"".to_string(),
11488                        end: "\"".to_string(),
11489                        close: true,
11490                        surround: true,
11491                        newline: false,
11492                    },
11493                    BracketPair {
11494                        start: "<".to_string(),
11495                        end: ">".to_string(),
11496                        close: false,
11497                        surround: true,
11498                        newline: true,
11499                    },
11500                ],
11501                ..Default::default()
11502            },
11503            autoclose_before: "})]".to_string(),
11504            ..Default::default()
11505        },
11506        Some(tree_sitter_rust::LANGUAGE.into()),
11507    );
11508    let language = Arc::new(language);
11509
11510    cx.language_registry().add(language.clone());
11511    cx.update_buffer(|buffer, cx| {
11512        buffer.set_language(Some(language), cx);
11513    });
11514
11515    // Ensure that signature_help is not called when no signature help is enabled.
11516    cx.set_state(
11517        &r#"
11518            fn main() {
11519                sampleˇ
11520            }
11521        "#
11522        .unindent(),
11523    );
11524    cx.update_editor(|editor, window, cx| {
11525        editor.handle_input("(", window, cx);
11526    });
11527    cx.assert_editor_state(
11528        &"
11529            fn main() {
11530                sample(ˇ)
11531            }
11532        "
11533        .unindent(),
11534    );
11535    cx.editor(|editor, _, _| {
11536        assert!(editor.signature_help_state.task().is_none());
11537    });
11538
11539    let mocked_response = lsp::SignatureHelp {
11540        signatures: vec![lsp::SignatureInformation {
11541            label: "fn sample(param1: u8, param2: u8)".to_string(),
11542            documentation: None,
11543            parameters: Some(vec![
11544                lsp::ParameterInformation {
11545                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11546                    documentation: None,
11547                },
11548                lsp::ParameterInformation {
11549                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11550                    documentation: None,
11551                },
11552            ]),
11553            active_parameter: None,
11554        }],
11555        active_signature: Some(0),
11556        active_parameter: Some(0),
11557    };
11558
11559    // Ensure that signature_help is called when enabled afte edits
11560    cx.update(|_, cx| {
11561        cx.update_global::<SettingsStore, _>(|settings, cx| {
11562            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11563                settings.auto_signature_help = Some(false);
11564                settings.show_signature_help_after_edits = Some(true);
11565            });
11566        });
11567    });
11568    cx.set_state(
11569        &r#"
11570            fn main() {
11571                sampleˇ
11572            }
11573        "#
11574        .unindent(),
11575    );
11576    cx.update_editor(|editor, window, cx| {
11577        editor.handle_input("(", window, cx);
11578    });
11579    cx.assert_editor_state(
11580        &"
11581            fn main() {
11582                sample(ˇ)
11583            }
11584        "
11585        .unindent(),
11586    );
11587    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11588    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11589        .await;
11590    cx.update_editor(|editor, _, _| {
11591        let signature_help_state = editor.signature_help_state.popover().cloned();
11592        assert!(signature_help_state.is_some());
11593        let signature = signature_help_state.unwrap();
11594        assert_eq!(
11595            signature.signatures[signature.current_signature].label,
11596            "fn sample(param1: u8, param2: u8)"
11597        );
11598        editor.signature_help_state = SignatureHelpState::default();
11599    });
11600
11601    // Ensure that signature_help is called when auto signature help override is enabled
11602    cx.update(|_, cx| {
11603        cx.update_global::<SettingsStore, _>(|settings, cx| {
11604            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11605                settings.auto_signature_help = Some(true);
11606                settings.show_signature_help_after_edits = Some(false);
11607            });
11608        });
11609    });
11610    cx.set_state(
11611        &r#"
11612            fn main() {
11613                sampleˇ
11614            }
11615        "#
11616        .unindent(),
11617    );
11618    cx.update_editor(|editor, window, cx| {
11619        editor.handle_input("(", window, cx);
11620    });
11621    cx.assert_editor_state(
11622        &"
11623            fn main() {
11624                sample(ˇ)
11625            }
11626        "
11627        .unindent(),
11628    );
11629    handle_signature_help_request(&mut cx, mocked_response).await;
11630    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11631        .await;
11632    cx.editor(|editor, _, _| {
11633        let signature_help_state = editor.signature_help_state.popover().cloned();
11634        assert!(signature_help_state.is_some());
11635        let signature = signature_help_state.unwrap();
11636        assert_eq!(
11637            signature.signatures[signature.current_signature].label,
11638            "fn sample(param1: u8, param2: u8)"
11639        );
11640    });
11641}
11642
11643#[gpui::test]
11644async fn test_signature_help(cx: &mut TestAppContext) {
11645    init_test(cx, |_| {});
11646    cx.update(|cx| {
11647        cx.update_global::<SettingsStore, _>(|settings, cx| {
11648            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11649                settings.auto_signature_help = Some(true);
11650            });
11651        });
11652    });
11653
11654    let mut cx = EditorLspTestContext::new_rust(
11655        lsp::ServerCapabilities {
11656            signature_help_provider: Some(lsp::SignatureHelpOptions {
11657                ..Default::default()
11658            }),
11659            ..Default::default()
11660        },
11661        cx,
11662    )
11663    .await;
11664
11665    // A test that directly calls `show_signature_help`
11666    cx.update_editor(|editor, window, cx| {
11667        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11668    });
11669
11670    let mocked_response = lsp::SignatureHelp {
11671        signatures: vec![lsp::SignatureInformation {
11672            label: "fn sample(param1: u8, param2: u8)".to_string(),
11673            documentation: None,
11674            parameters: Some(vec![
11675                lsp::ParameterInformation {
11676                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11677                    documentation: None,
11678                },
11679                lsp::ParameterInformation {
11680                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11681                    documentation: None,
11682                },
11683            ]),
11684            active_parameter: None,
11685        }],
11686        active_signature: Some(0),
11687        active_parameter: Some(0),
11688    };
11689    handle_signature_help_request(&mut cx, mocked_response).await;
11690
11691    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11692        .await;
11693
11694    cx.editor(|editor, _, _| {
11695        let signature_help_state = editor.signature_help_state.popover().cloned();
11696        assert!(signature_help_state.is_some());
11697        let signature = signature_help_state.unwrap();
11698        assert_eq!(
11699            signature.signatures[signature.current_signature].label,
11700            "fn sample(param1: u8, param2: u8)"
11701        );
11702    });
11703
11704    // When exiting outside from inside the brackets, `signature_help` is closed.
11705    cx.set_state(indoc! {"
11706        fn main() {
11707            sample(ˇ);
11708        }
11709
11710        fn sample(param1: u8, param2: u8) {}
11711    "});
11712
11713    cx.update_editor(|editor, window, cx| {
11714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11715            s.select_ranges([0..0])
11716        });
11717    });
11718
11719    let mocked_response = lsp::SignatureHelp {
11720        signatures: Vec::new(),
11721        active_signature: None,
11722        active_parameter: None,
11723    };
11724    handle_signature_help_request(&mut cx, mocked_response).await;
11725
11726    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11727        .await;
11728
11729    cx.editor(|editor, _, _| {
11730        assert!(!editor.signature_help_state.is_shown());
11731    });
11732
11733    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11734    cx.set_state(indoc! {"
11735        fn main() {
11736            sample(ˇ);
11737        }
11738
11739        fn sample(param1: u8, param2: u8) {}
11740    "});
11741
11742    let mocked_response = lsp::SignatureHelp {
11743        signatures: vec![lsp::SignatureInformation {
11744            label: "fn sample(param1: u8, param2: u8)".to_string(),
11745            documentation: None,
11746            parameters: Some(vec![
11747                lsp::ParameterInformation {
11748                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11749                    documentation: None,
11750                },
11751                lsp::ParameterInformation {
11752                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11753                    documentation: None,
11754                },
11755            ]),
11756            active_parameter: None,
11757        }],
11758        active_signature: Some(0),
11759        active_parameter: Some(0),
11760    };
11761    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11762    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11763        .await;
11764    cx.editor(|editor, _, _| {
11765        assert!(editor.signature_help_state.is_shown());
11766    });
11767
11768    // Restore the popover with more parameter input
11769    cx.set_state(indoc! {"
11770        fn main() {
11771            sample(param1, param2ˇ);
11772        }
11773
11774        fn sample(param1: u8, param2: u8) {}
11775    "});
11776
11777    let mocked_response = lsp::SignatureHelp {
11778        signatures: vec![lsp::SignatureInformation {
11779            label: "fn sample(param1: u8, param2: u8)".to_string(),
11780            documentation: None,
11781            parameters: Some(vec![
11782                lsp::ParameterInformation {
11783                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11784                    documentation: None,
11785                },
11786                lsp::ParameterInformation {
11787                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11788                    documentation: None,
11789                },
11790            ]),
11791            active_parameter: None,
11792        }],
11793        active_signature: Some(0),
11794        active_parameter: Some(1),
11795    };
11796    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11797    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11798        .await;
11799
11800    // When selecting a range, the popover is gone.
11801    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11802    cx.update_editor(|editor, window, cx| {
11803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11804            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11805        })
11806    });
11807    cx.assert_editor_state(indoc! {"
11808        fn main() {
11809            sample(param1, «ˇparam2»);
11810        }
11811
11812        fn sample(param1: u8, param2: u8) {}
11813    "});
11814    cx.editor(|editor, _, _| {
11815        assert!(!editor.signature_help_state.is_shown());
11816    });
11817
11818    // When unselecting again, the popover is back if within the brackets.
11819    cx.update_editor(|editor, window, cx| {
11820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11821            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11822        })
11823    });
11824    cx.assert_editor_state(indoc! {"
11825        fn main() {
11826            sample(param1, ˇparam2);
11827        }
11828
11829        fn sample(param1: u8, param2: u8) {}
11830    "});
11831    handle_signature_help_request(&mut cx, mocked_response).await;
11832    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11833        .await;
11834    cx.editor(|editor, _, _| {
11835        assert!(editor.signature_help_state.is_shown());
11836    });
11837
11838    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11839    cx.update_editor(|editor, window, cx| {
11840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11841            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11842            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11843        })
11844    });
11845    cx.assert_editor_state(indoc! {"
11846        fn main() {
11847            sample(param1, ˇparam2);
11848        }
11849
11850        fn sample(param1: u8, param2: u8) {}
11851    "});
11852
11853    let mocked_response = lsp::SignatureHelp {
11854        signatures: vec![lsp::SignatureInformation {
11855            label: "fn sample(param1: u8, param2: u8)".to_string(),
11856            documentation: None,
11857            parameters: Some(vec![
11858                lsp::ParameterInformation {
11859                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11860                    documentation: None,
11861                },
11862                lsp::ParameterInformation {
11863                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11864                    documentation: None,
11865                },
11866            ]),
11867            active_parameter: None,
11868        }],
11869        active_signature: Some(0),
11870        active_parameter: Some(1),
11871    };
11872    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11873    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11874        .await;
11875    cx.update_editor(|editor, _, cx| {
11876        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11877    });
11878    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11879        .await;
11880    cx.update_editor(|editor, window, cx| {
11881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11882            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11883        })
11884    });
11885    cx.assert_editor_state(indoc! {"
11886        fn main() {
11887            sample(param1, «ˇparam2»);
11888        }
11889
11890        fn sample(param1: u8, param2: u8) {}
11891    "});
11892    cx.update_editor(|editor, window, cx| {
11893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11894            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11895        })
11896    });
11897    cx.assert_editor_state(indoc! {"
11898        fn main() {
11899            sample(param1, ˇparam2);
11900        }
11901
11902        fn sample(param1: u8, param2: u8) {}
11903    "});
11904    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11905        .await;
11906}
11907
11908#[gpui::test]
11909async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11910    init_test(cx, |_| {});
11911
11912    let mut cx = EditorLspTestContext::new_rust(
11913        lsp::ServerCapabilities {
11914            signature_help_provider: Some(lsp::SignatureHelpOptions {
11915                ..Default::default()
11916            }),
11917            ..Default::default()
11918        },
11919        cx,
11920    )
11921    .await;
11922
11923    cx.set_state(indoc! {"
11924        fn main() {
11925            overloadedˇ
11926        }
11927    "});
11928
11929    cx.update_editor(|editor, window, cx| {
11930        editor.handle_input("(", window, cx);
11931        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11932    });
11933
11934    // Mock response with 3 signatures
11935    let mocked_response = lsp::SignatureHelp {
11936        signatures: vec![
11937            lsp::SignatureInformation {
11938                label: "fn overloaded(x: i32)".to_string(),
11939                documentation: None,
11940                parameters: Some(vec![lsp::ParameterInformation {
11941                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11942                    documentation: None,
11943                }]),
11944                active_parameter: None,
11945            },
11946            lsp::SignatureInformation {
11947                label: "fn overloaded(x: i32, y: i32)".to_string(),
11948                documentation: None,
11949                parameters: Some(vec![
11950                    lsp::ParameterInformation {
11951                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11952                        documentation: None,
11953                    },
11954                    lsp::ParameterInformation {
11955                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11956                        documentation: None,
11957                    },
11958                ]),
11959                active_parameter: None,
11960            },
11961            lsp::SignatureInformation {
11962                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11963                documentation: None,
11964                parameters: Some(vec![
11965                    lsp::ParameterInformation {
11966                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11967                        documentation: None,
11968                    },
11969                    lsp::ParameterInformation {
11970                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11971                        documentation: None,
11972                    },
11973                    lsp::ParameterInformation {
11974                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11975                        documentation: None,
11976                    },
11977                ]),
11978                active_parameter: None,
11979            },
11980        ],
11981        active_signature: Some(1),
11982        active_parameter: Some(0),
11983    };
11984    handle_signature_help_request(&mut cx, mocked_response).await;
11985
11986    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11987        .await;
11988
11989    // Verify we have multiple signatures and the right one is selected
11990    cx.editor(|editor, _, _| {
11991        let popover = editor.signature_help_state.popover().cloned().unwrap();
11992        assert_eq!(popover.signatures.len(), 3);
11993        // active_signature was 1, so that should be the current
11994        assert_eq!(popover.current_signature, 1);
11995        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11996        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11997        assert_eq!(
11998            popover.signatures[2].label,
11999            "fn overloaded(x: i32, y: i32, z: i32)"
12000        );
12001    });
12002
12003    // Test navigation functionality
12004    cx.update_editor(|editor, window, cx| {
12005        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12006    });
12007
12008    cx.editor(|editor, _, _| {
12009        let popover = editor.signature_help_state.popover().cloned().unwrap();
12010        assert_eq!(popover.current_signature, 2);
12011    });
12012
12013    // Test wrap around
12014    cx.update_editor(|editor, window, cx| {
12015        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12016    });
12017
12018    cx.editor(|editor, _, _| {
12019        let popover = editor.signature_help_state.popover().cloned().unwrap();
12020        assert_eq!(popover.current_signature, 0);
12021    });
12022
12023    // Test previous navigation
12024    cx.update_editor(|editor, window, cx| {
12025        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12026    });
12027
12028    cx.editor(|editor, _, _| {
12029        let popover = editor.signature_help_state.popover().cloned().unwrap();
12030        assert_eq!(popover.current_signature, 2);
12031    });
12032}
12033
12034#[gpui::test]
12035async fn test_completion_mode(cx: &mut TestAppContext) {
12036    init_test(cx, |_| {});
12037    let mut cx = EditorLspTestContext::new_rust(
12038        lsp::ServerCapabilities {
12039            completion_provider: Some(lsp::CompletionOptions {
12040                resolve_provider: Some(true),
12041                ..Default::default()
12042            }),
12043            ..Default::default()
12044        },
12045        cx,
12046    )
12047    .await;
12048
12049    struct Run {
12050        run_description: &'static str,
12051        initial_state: String,
12052        buffer_marked_text: String,
12053        completion_label: &'static str,
12054        completion_text: &'static str,
12055        expected_with_insert_mode: String,
12056        expected_with_replace_mode: String,
12057        expected_with_replace_subsequence_mode: String,
12058        expected_with_replace_suffix_mode: String,
12059    }
12060
12061    let runs = [
12062        Run {
12063            run_description: "Start of word matches completion text",
12064            initial_state: "before ediˇ after".into(),
12065            buffer_marked_text: "before <edi|> after".into(),
12066            completion_label: "editor",
12067            completion_text: "editor",
12068            expected_with_insert_mode: "before editorˇ after".into(),
12069            expected_with_replace_mode: "before editorˇ after".into(),
12070            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12071            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12072        },
12073        Run {
12074            run_description: "Accept same text at the middle of the word",
12075            initial_state: "before ediˇtor after".into(),
12076            buffer_marked_text: "before <edi|tor> after".into(),
12077            completion_label: "editor",
12078            completion_text: "editor",
12079            expected_with_insert_mode: "before editorˇtor after".into(),
12080            expected_with_replace_mode: "before editorˇ after".into(),
12081            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12082            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12083        },
12084        Run {
12085            run_description: "End of word matches completion text -- cursor at end",
12086            initial_state: "before torˇ after".into(),
12087            buffer_marked_text: "before <tor|> after".into(),
12088            completion_label: "editor",
12089            completion_text: "editor",
12090            expected_with_insert_mode: "before editorˇ after".into(),
12091            expected_with_replace_mode: "before editorˇ after".into(),
12092            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12093            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12094        },
12095        Run {
12096            run_description: "End of word matches completion text -- cursor at start",
12097            initial_state: "before ˇtor after".into(),
12098            buffer_marked_text: "before <|tor> after".into(),
12099            completion_label: "editor",
12100            completion_text: "editor",
12101            expected_with_insert_mode: "before editorˇtor after".into(),
12102            expected_with_replace_mode: "before editorˇ after".into(),
12103            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12104            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12105        },
12106        Run {
12107            run_description: "Prepend text containing whitespace",
12108            initial_state: "pˇfield: bool".into(),
12109            buffer_marked_text: "<p|field>: bool".into(),
12110            completion_label: "pub ",
12111            completion_text: "pub ",
12112            expected_with_insert_mode: "pub ˇfield: bool".into(),
12113            expected_with_replace_mode: "pub ˇ: bool".into(),
12114            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12115            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12116        },
12117        Run {
12118            run_description: "Add element to start of list",
12119            initial_state: "[element_ˇelement_2]".into(),
12120            buffer_marked_text: "[<element_|element_2>]".into(),
12121            completion_label: "element_1",
12122            completion_text: "element_1",
12123            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12124            expected_with_replace_mode: "[element_1ˇ]".into(),
12125            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12126            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12127        },
12128        Run {
12129            run_description: "Add element to start of list -- first and second elements are equal",
12130            initial_state: "[elˇelement]".into(),
12131            buffer_marked_text: "[<el|element>]".into(),
12132            completion_label: "element",
12133            completion_text: "element",
12134            expected_with_insert_mode: "[elementˇelement]".into(),
12135            expected_with_replace_mode: "[elementˇ]".into(),
12136            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12137            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12138        },
12139        Run {
12140            run_description: "Ends with matching suffix",
12141            initial_state: "SubˇError".into(),
12142            buffer_marked_text: "<Sub|Error>".into(),
12143            completion_label: "SubscriptionError",
12144            completion_text: "SubscriptionError",
12145            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12146            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12147            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12148            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12149        },
12150        Run {
12151            run_description: "Suffix is a subsequence -- contiguous",
12152            initial_state: "SubˇErr".into(),
12153            buffer_marked_text: "<Sub|Err>".into(),
12154            completion_label: "SubscriptionError",
12155            completion_text: "SubscriptionError",
12156            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12157            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12158            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12159            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12160        },
12161        Run {
12162            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12163            initial_state: "Suˇscrirr".into(),
12164            buffer_marked_text: "<Su|scrirr>".into(),
12165            completion_label: "SubscriptionError",
12166            completion_text: "SubscriptionError",
12167            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12168            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12169            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12170            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12171        },
12172        Run {
12173            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12174            initial_state: "foo(indˇix)".into(),
12175            buffer_marked_text: "foo(<ind|ix>)".into(),
12176            completion_label: "node_index",
12177            completion_text: "node_index",
12178            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12179            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12180            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12181            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12182        },
12183        Run {
12184            run_description: "Replace range ends before cursor - should extend to cursor",
12185            initial_state: "before editˇo after".into(),
12186            buffer_marked_text: "before <{ed}>it|o after".into(),
12187            completion_label: "editor",
12188            completion_text: "editor",
12189            expected_with_insert_mode: "before editorˇo after".into(),
12190            expected_with_replace_mode: "before editorˇo after".into(),
12191            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12192            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12193        },
12194        Run {
12195            run_description: "Uses label for suffix matching",
12196            initial_state: "before ediˇtor after".into(),
12197            buffer_marked_text: "before <edi|tor> after".into(),
12198            completion_label: "editor",
12199            completion_text: "editor()",
12200            expected_with_insert_mode: "before editor()ˇtor after".into(),
12201            expected_with_replace_mode: "before editor()ˇ after".into(),
12202            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12203            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12204        },
12205        Run {
12206            run_description: "Case insensitive subsequence and suffix matching",
12207            initial_state: "before EDiˇtoR after".into(),
12208            buffer_marked_text: "before <EDi|toR> after".into(),
12209            completion_label: "editor",
12210            completion_text: "editor",
12211            expected_with_insert_mode: "before editorˇtoR after".into(),
12212            expected_with_replace_mode: "before editorˇ after".into(),
12213            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12214            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12215        },
12216    ];
12217
12218    for run in runs {
12219        let run_variations = [
12220            (LspInsertMode::Insert, run.expected_with_insert_mode),
12221            (LspInsertMode::Replace, run.expected_with_replace_mode),
12222            (
12223                LspInsertMode::ReplaceSubsequence,
12224                run.expected_with_replace_subsequence_mode,
12225            ),
12226            (
12227                LspInsertMode::ReplaceSuffix,
12228                run.expected_with_replace_suffix_mode,
12229            ),
12230        ];
12231
12232        for (lsp_insert_mode, expected_text) in run_variations {
12233            eprintln!(
12234                "run = {:?}, mode = {lsp_insert_mode:.?}",
12235                run.run_description,
12236            );
12237
12238            update_test_language_settings(&mut cx, |settings| {
12239                settings.defaults.completions = Some(CompletionSettings {
12240                    lsp_insert_mode,
12241                    words: WordsCompletionMode::Disabled,
12242                    words_min_length: 0,
12243                    lsp: true,
12244                    lsp_fetch_timeout_ms: 0,
12245                });
12246            });
12247
12248            cx.set_state(&run.initial_state);
12249            cx.update_editor(|editor, window, cx| {
12250                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12251            });
12252
12253            let counter = Arc::new(AtomicUsize::new(0));
12254            handle_completion_request_with_insert_and_replace(
12255                &mut cx,
12256                &run.buffer_marked_text,
12257                vec![(run.completion_label, run.completion_text)],
12258                counter.clone(),
12259            )
12260            .await;
12261            cx.condition(|editor, _| editor.context_menu_visible())
12262                .await;
12263            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12264
12265            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12266                editor
12267                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12268                    .unwrap()
12269            });
12270            cx.assert_editor_state(&expected_text);
12271            handle_resolve_completion_request(&mut cx, None).await;
12272            apply_additional_edits.await.unwrap();
12273        }
12274    }
12275}
12276
12277#[gpui::test]
12278async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12279    init_test(cx, |_| {});
12280    let mut cx = EditorLspTestContext::new_rust(
12281        lsp::ServerCapabilities {
12282            completion_provider: Some(lsp::CompletionOptions {
12283                resolve_provider: Some(true),
12284                ..Default::default()
12285            }),
12286            ..Default::default()
12287        },
12288        cx,
12289    )
12290    .await;
12291
12292    let initial_state = "SubˇError";
12293    let buffer_marked_text = "<Sub|Error>";
12294    let completion_text = "SubscriptionError";
12295    let expected_with_insert_mode = "SubscriptionErrorˇError";
12296    let expected_with_replace_mode = "SubscriptionErrorˇ";
12297
12298    update_test_language_settings(&mut cx, |settings| {
12299        settings.defaults.completions = Some(CompletionSettings {
12300            words: WordsCompletionMode::Disabled,
12301            words_min_length: 0,
12302            // set the opposite here to ensure that the action is overriding the default behavior
12303            lsp_insert_mode: LspInsertMode::Insert,
12304            lsp: true,
12305            lsp_fetch_timeout_ms: 0,
12306        });
12307    });
12308
12309    cx.set_state(initial_state);
12310    cx.update_editor(|editor, window, cx| {
12311        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12312    });
12313
12314    let counter = Arc::new(AtomicUsize::new(0));
12315    handle_completion_request_with_insert_and_replace(
12316        &mut cx,
12317        buffer_marked_text,
12318        vec![(completion_text, completion_text)],
12319        counter.clone(),
12320    )
12321    .await;
12322    cx.condition(|editor, _| editor.context_menu_visible())
12323        .await;
12324    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12325
12326    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12327        editor
12328            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12329            .unwrap()
12330    });
12331    cx.assert_editor_state(expected_with_replace_mode);
12332    handle_resolve_completion_request(&mut cx, None).await;
12333    apply_additional_edits.await.unwrap();
12334
12335    update_test_language_settings(&mut cx, |settings| {
12336        settings.defaults.completions = Some(CompletionSettings {
12337            words: WordsCompletionMode::Disabled,
12338            words_min_length: 0,
12339            // set the opposite here to ensure that the action is overriding the default behavior
12340            lsp_insert_mode: LspInsertMode::Replace,
12341            lsp: true,
12342            lsp_fetch_timeout_ms: 0,
12343        });
12344    });
12345
12346    cx.set_state(initial_state);
12347    cx.update_editor(|editor, window, cx| {
12348        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12349    });
12350    handle_completion_request_with_insert_and_replace(
12351        &mut cx,
12352        buffer_marked_text,
12353        vec![(completion_text, completion_text)],
12354        counter.clone(),
12355    )
12356    .await;
12357    cx.condition(|editor, _| editor.context_menu_visible())
12358        .await;
12359    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12360
12361    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12362        editor
12363            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12364            .unwrap()
12365    });
12366    cx.assert_editor_state(expected_with_insert_mode);
12367    handle_resolve_completion_request(&mut cx, None).await;
12368    apply_additional_edits.await.unwrap();
12369}
12370
12371#[gpui::test]
12372async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12373    init_test(cx, |_| {});
12374    let mut cx = EditorLspTestContext::new_rust(
12375        lsp::ServerCapabilities {
12376            completion_provider: Some(lsp::CompletionOptions {
12377                resolve_provider: Some(true),
12378                ..Default::default()
12379            }),
12380            ..Default::default()
12381        },
12382        cx,
12383    )
12384    .await;
12385
12386    // scenario: surrounding text matches completion text
12387    let completion_text = "to_offset";
12388    let initial_state = indoc! {"
12389        1. buf.to_offˇsuffix
12390        2. buf.to_offˇsuf
12391        3. buf.to_offˇfix
12392        4. buf.to_offˇ
12393        5. into_offˇensive
12394        6. ˇsuffix
12395        7. let ˇ //
12396        8. aaˇzz
12397        9. buf.to_off«zzzzzˇ»suffix
12398        10. buf.«ˇzzzzz»suffix
12399        11. to_off«ˇzzzzz»
12400
12401        buf.to_offˇsuffix  // newest cursor
12402    "};
12403    let completion_marked_buffer = indoc! {"
12404        1. buf.to_offsuffix
12405        2. buf.to_offsuf
12406        3. buf.to_offfix
12407        4. buf.to_off
12408        5. into_offensive
12409        6. suffix
12410        7. let  //
12411        8. aazz
12412        9. buf.to_offzzzzzsuffix
12413        10. buf.zzzzzsuffix
12414        11. to_offzzzzz
12415
12416        buf.<to_off|suffix>  // newest cursor
12417    "};
12418    let expected = indoc! {"
12419        1. buf.to_offsetˇ
12420        2. buf.to_offsetˇsuf
12421        3. buf.to_offsetˇfix
12422        4. buf.to_offsetˇ
12423        5. into_offsetˇensive
12424        6. to_offsetˇsuffix
12425        7. let to_offsetˇ //
12426        8. aato_offsetˇzz
12427        9. buf.to_offsetˇ
12428        10. buf.to_offsetˇsuffix
12429        11. to_offsetˇ
12430
12431        buf.to_offsetˇ  // newest cursor
12432    "};
12433    cx.set_state(initial_state);
12434    cx.update_editor(|editor, window, cx| {
12435        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12436    });
12437    handle_completion_request_with_insert_and_replace(
12438        &mut cx,
12439        completion_marked_buffer,
12440        vec![(completion_text, completion_text)],
12441        Arc::new(AtomicUsize::new(0)),
12442    )
12443    .await;
12444    cx.condition(|editor, _| editor.context_menu_visible())
12445        .await;
12446    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12447        editor
12448            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12449            .unwrap()
12450    });
12451    cx.assert_editor_state(expected);
12452    handle_resolve_completion_request(&mut cx, None).await;
12453    apply_additional_edits.await.unwrap();
12454
12455    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12456    let completion_text = "foo_and_bar";
12457    let initial_state = indoc! {"
12458        1. ooanbˇ
12459        2. zooanbˇ
12460        3. ooanbˇz
12461        4. zooanbˇz
12462        5. ooanˇ
12463        6. oanbˇ
12464
12465        ooanbˇ
12466    "};
12467    let completion_marked_buffer = indoc! {"
12468        1. ooanb
12469        2. zooanb
12470        3. ooanbz
12471        4. zooanbz
12472        5. ooan
12473        6. oanb
12474
12475        <ooanb|>
12476    "};
12477    let expected = indoc! {"
12478        1. foo_and_barˇ
12479        2. zfoo_and_barˇ
12480        3. foo_and_barˇz
12481        4. zfoo_and_barˇz
12482        5. ooanfoo_and_barˇ
12483        6. oanbfoo_and_barˇ
12484
12485        foo_and_barˇ
12486    "};
12487    cx.set_state(initial_state);
12488    cx.update_editor(|editor, window, cx| {
12489        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12490    });
12491    handle_completion_request_with_insert_and_replace(
12492        &mut cx,
12493        completion_marked_buffer,
12494        vec![(completion_text, completion_text)],
12495        Arc::new(AtomicUsize::new(0)),
12496    )
12497    .await;
12498    cx.condition(|editor, _| editor.context_menu_visible())
12499        .await;
12500    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12501        editor
12502            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12503            .unwrap()
12504    });
12505    cx.assert_editor_state(expected);
12506    handle_resolve_completion_request(&mut cx, None).await;
12507    apply_additional_edits.await.unwrap();
12508
12509    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12510    // (expects the same as if it was inserted at the end)
12511    let completion_text = "foo_and_bar";
12512    let initial_state = indoc! {"
12513        1. ooˇanb
12514        2. zooˇanb
12515        3. ooˇanbz
12516        4. zooˇanbz
12517
12518        ooˇanb
12519    "};
12520    let completion_marked_buffer = indoc! {"
12521        1. ooanb
12522        2. zooanb
12523        3. ooanbz
12524        4. zooanbz
12525
12526        <oo|anb>
12527    "};
12528    let expected = indoc! {"
12529        1. foo_and_barˇ
12530        2. zfoo_and_barˇ
12531        3. foo_and_barˇz
12532        4. zfoo_and_barˇz
12533
12534        foo_and_barˇ
12535    "};
12536    cx.set_state(initial_state);
12537    cx.update_editor(|editor, window, cx| {
12538        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12539    });
12540    handle_completion_request_with_insert_and_replace(
12541        &mut cx,
12542        completion_marked_buffer,
12543        vec![(completion_text, completion_text)],
12544        Arc::new(AtomicUsize::new(0)),
12545    )
12546    .await;
12547    cx.condition(|editor, _| editor.context_menu_visible())
12548        .await;
12549    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12550        editor
12551            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12552            .unwrap()
12553    });
12554    cx.assert_editor_state(expected);
12555    handle_resolve_completion_request(&mut cx, None).await;
12556    apply_additional_edits.await.unwrap();
12557}
12558
12559// This used to crash
12560#[gpui::test]
12561async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12562    init_test(cx, |_| {});
12563
12564    let buffer_text = indoc! {"
12565        fn main() {
12566            10.satu;
12567
12568            //
12569            // separate cursors so they open in different excerpts (manually reproducible)
12570            //
12571
12572            10.satu20;
12573        }
12574    "};
12575    let multibuffer_text_with_selections = indoc! {"
12576        fn main() {
12577            10.satuˇ;
12578
12579            //
12580
12581            //
12582
12583            10.satuˇ20;
12584        }
12585    "};
12586    let expected_multibuffer = indoc! {"
12587        fn main() {
12588            10.saturating_sub()ˇ;
12589
12590            //
12591
12592            //
12593
12594            10.saturating_sub()ˇ;
12595        }
12596    "};
12597
12598    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12599    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12600
12601    let fs = FakeFs::new(cx.executor());
12602    fs.insert_tree(
12603        path!("/a"),
12604        json!({
12605            "main.rs": buffer_text,
12606        }),
12607    )
12608    .await;
12609
12610    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12611    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12612    language_registry.add(rust_lang());
12613    let mut fake_servers = language_registry.register_fake_lsp(
12614        "Rust",
12615        FakeLspAdapter {
12616            capabilities: lsp::ServerCapabilities {
12617                completion_provider: Some(lsp::CompletionOptions {
12618                    resolve_provider: None,
12619                    ..lsp::CompletionOptions::default()
12620                }),
12621                ..lsp::ServerCapabilities::default()
12622            },
12623            ..FakeLspAdapter::default()
12624        },
12625    );
12626    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12627    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12628    let buffer = project
12629        .update(cx, |project, cx| {
12630            project.open_local_buffer(path!("/a/main.rs"), cx)
12631        })
12632        .await
12633        .unwrap();
12634
12635    let multi_buffer = cx.new(|cx| {
12636        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12637        multi_buffer.push_excerpts(
12638            buffer.clone(),
12639            [ExcerptRange::new(0..first_excerpt_end)],
12640            cx,
12641        );
12642        multi_buffer.push_excerpts(
12643            buffer.clone(),
12644            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12645            cx,
12646        );
12647        multi_buffer
12648    });
12649
12650    let editor = workspace
12651        .update(cx, |_, window, cx| {
12652            cx.new(|cx| {
12653                Editor::new(
12654                    EditorMode::Full {
12655                        scale_ui_elements_with_buffer_font_size: false,
12656                        show_active_line_background: false,
12657                        sized_by_content: false,
12658                    },
12659                    multi_buffer.clone(),
12660                    Some(project.clone()),
12661                    window,
12662                    cx,
12663                )
12664            })
12665        })
12666        .unwrap();
12667
12668    let pane = workspace
12669        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12670        .unwrap();
12671    pane.update_in(cx, |pane, window, cx| {
12672        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12673    });
12674
12675    let fake_server = fake_servers.next().await.unwrap();
12676
12677    editor.update_in(cx, |editor, window, cx| {
12678        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12679            s.select_ranges([
12680                Point::new(1, 11)..Point::new(1, 11),
12681                Point::new(7, 11)..Point::new(7, 11),
12682            ])
12683        });
12684
12685        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12686    });
12687
12688    editor.update_in(cx, |editor, window, cx| {
12689        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12690    });
12691
12692    fake_server
12693        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12694            let completion_item = lsp::CompletionItem {
12695                label: "saturating_sub()".into(),
12696                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12697                    lsp::InsertReplaceEdit {
12698                        new_text: "saturating_sub()".to_owned(),
12699                        insert: lsp::Range::new(
12700                            lsp::Position::new(7, 7),
12701                            lsp::Position::new(7, 11),
12702                        ),
12703                        replace: lsp::Range::new(
12704                            lsp::Position::new(7, 7),
12705                            lsp::Position::new(7, 13),
12706                        ),
12707                    },
12708                )),
12709                ..lsp::CompletionItem::default()
12710            };
12711
12712            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12713        })
12714        .next()
12715        .await
12716        .unwrap();
12717
12718    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12719        .await;
12720
12721    editor
12722        .update_in(cx, |editor, window, cx| {
12723            editor
12724                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12725                .unwrap()
12726        })
12727        .await
12728        .unwrap();
12729
12730    editor.update(cx, |editor, cx| {
12731        assert_text_with_selections(editor, expected_multibuffer, cx);
12732    })
12733}
12734
12735#[gpui::test]
12736async fn test_completion(cx: &mut TestAppContext) {
12737    init_test(cx, |_| {});
12738
12739    let mut cx = EditorLspTestContext::new_rust(
12740        lsp::ServerCapabilities {
12741            completion_provider: Some(lsp::CompletionOptions {
12742                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12743                resolve_provider: Some(true),
12744                ..Default::default()
12745            }),
12746            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12747            ..Default::default()
12748        },
12749        cx,
12750    )
12751    .await;
12752    let counter = Arc::new(AtomicUsize::new(0));
12753
12754    cx.set_state(indoc! {"
12755        oneˇ
12756        two
12757        three
12758    "});
12759    cx.simulate_keystroke(".");
12760    handle_completion_request(
12761        indoc! {"
12762            one.|<>
12763            two
12764            three
12765        "},
12766        vec!["first_completion", "second_completion"],
12767        true,
12768        counter.clone(),
12769        &mut cx,
12770    )
12771    .await;
12772    cx.condition(|editor, _| editor.context_menu_visible())
12773        .await;
12774    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12775
12776    let _handler = handle_signature_help_request(
12777        &mut cx,
12778        lsp::SignatureHelp {
12779            signatures: vec![lsp::SignatureInformation {
12780                label: "test signature".to_string(),
12781                documentation: None,
12782                parameters: Some(vec![lsp::ParameterInformation {
12783                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12784                    documentation: None,
12785                }]),
12786                active_parameter: None,
12787            }],
12788            active_signature: None,
12789            active_parameter: None,
12790        },
12791    );
12792    cx.update_editor(|editor, window, cx| {
12793        assert!(
12794            !editor.signature_help_state.is_shown(),
12795            "No signature help was called for"
12796        );
12797        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12798    });
12799    cx.run_until_parked();
12800    cx.update_editor(|editor, _, _| {
12801        assert!(
12802            !editor.signature_help_state.is_shown(),
12803            "No signature help should be shown when completions menu is open"
12804        );
12805    });
12806
12807    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12808        editor.context_menu_next(&Default::default(), window, cx);
12809        editor
12810            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12811            .unwrap()
12812    });
12813    cx.assert_editor_state(indoc! {"
12814        one.second_completionˇ
12815        two
12816        three
12817    "});
12818
12819    handle_resolve_completion_request(
12820        &mut cx,
12821        Some(vec![
12822            (
12823                //This overlaps with the primary completion edit which is
12824                //misbehavior from the LSP spec, test that we filter it out
12825                indoc! {"
12826                    one.second_ˇcompletion
12827                    two
12828                    threeˇ
12829                "},
12830                "overlapping additional edit",
12831            ),
12832            (
12833                indoc! {"
12834                    one.second_completion
12835                    two
12836                    threeˇ
12837                "},
12838                "\nadditional edit",
12839            ),
12840        ]),
12841    )
12842    .await;
12843    apply_additional_edits.await.unwrap();
12844    cx.assert_editor_state(indoc! {"
12845        one.second_completionˇ
12846        two
12847        three
12848        additional edit
12849    "});
12850
12851    cx.set_state(indoc! {"
12852        one.second_completion
12853        twoˇ
12854        threeˇ
12855        additional edit
12856    "});
12857    cx.simulate_keystroke(" ");
12858    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12859    cx.simulate_keystroke("s");
12860    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12861
12862    cx.assert_editor_state(indoc! {"
12863        one.second_completion
12864        two sˇ
12865        three sˇ
12866        additional edit
12867    "});
12868    handle_completion_request(
12869        indoc! {"
12870            one.second_completion
12871            two s
12872            three <s|>
12873            additional edit
12874        "},
12875        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12876        true,
12877        counter.clone(),
12878        &mut cx,
12879    )
12880    .await;
12881    cx.condition(|editor, _| editor.context_menu_visible())
12882        .await;
12883    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12884
12885    cx.simulate_keystroke("i");
12886
12887    handle_completion_request(
12888        indoc! {"
12889            one.second_completion
12890            two si
12891            three <si|>
12892            additional edit
12893        "},
12894        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12895        true,
12896        counter.clone(),
12897        &mut cx,
12898    )
12899    .await;
12900    cx.condition(|editor, _| editor.context_menu_visible())
12901        .await;
12902    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12903
12904    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12905        editor
12906            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12907            .unwrap()
12908    });
12909    cx.assert_editor_state(indoc! {"
12910        one.second_completion
12911        two sixth_completionˇ
12912        three sixth_completionˇ
12913        additional edit
12914    "});
12915
12916    apply_additional_edits.await.unwrap();
12917
12918    update_test_language_settings(&mut cx, |settings| {
12919        settings.defaults.show_completions_on_input = Some(false);
12920    });
12921    cx.set_state("editorˇ");
12922    cx.simulate_keystroke(".");
12923    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12924    cx.simulate_keystrokes("c l o");
12925    cx.assert_editor_state("editor.cloˇ");
12926    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12927    cx.update_editor(|editor, window, cx| {
12928        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12929    });
12930    handle_completion_request(
12931        "editor.<clo|>",
12932        vec!["close", "clobber"],
12933        true,
12934        counter.clone(),
12935        &mut cx,
12936    )
12937    .await;
12938    cx.condition(|editor, _| editor.context_menu_visible())
12939        .await;
12940    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12941
12942    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12943        editor
12944            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12945            .unwrap()
12946    });
12947    cx.assert_editor_state("editor.clobberˇ");
12948    handle_resolve_completion_request(&mut cx, None).await;
12949    apply_additional_edits.await.unwrap();
12950}
12951
12952#[gpui::test]
12953async fn test_completion_reuse(cx: &mut TestAppContext) {
12954    init_test(cx, |_| {});
12955
12956    let mut cx = EditorLspTestContext::new_rust(
12957        lsp::ServerCapabilities {
12958            completion_provider: Some(lsp::CompletionOptions {
12959                trigger_characters: Some(vec![".".to_string()]),
12960                ..Default::default()
12961            }),
12962            ..Default::default()
12963        },
12964        cx,
12965    )
12966    .await;
12967
12968    let counter = Arc::new(AtomicUsize::new(0));
12969    cx.set_state("objˇ");
12970    cx.simulate_keystroke(".");
12971
12972    // Initial completion request returns complete results
12973    let is_incomplete = false;
12974    handle_completion_request(
12975        "obj.|<>",
12976        vec!["a", "ab", "abc"],
12977        is_incomplete,
12978        counter.clone(),
12979        &mut cx,
12980    )
12981    .await;
12982    cx.run_until_parked();
12983    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12984    cx.assert_editor_state("obj.ˇ");
12985    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12986
12987    // Type "a" - filters existing completions
12988    cx.simulate_keystroke("a");
12989    cx.run_until_parked();
12990    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12991    cx.assert_editor_state("obj.aˇ");
12992    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12993
12994    // Type "b" - filters existing completions
12995    cx.simulate_keystroke("b");
12996    cx.run_until_parked();
12997    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12998    cx.assert_editor_state("obj.abˇ");
12999    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13000
13001    // Type "c" - filters existing completions
13002    cx.simulate_keystroke("c");
13003    cx.run_until_parked();
13004    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13005    cx.assert_editor_state("obj.abcˇ");
13006    check_displayed_completions(vec!["abc"], &mut cx);
13007
13008    // Backspace to delete "c" - filters existing completions
13009    cx.update_editor(|editor, window, cx| {
13010        editor.backspace(&Backspace, window, cx);
13011    });
13012    cx.run_until_parked();
13013    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13014    cx.assert_editor_state("obj.abˇ");
13015    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13016
13017    // Moving cursor to the left dismisses menu.
13018    cx.update_editor(|editor, window, cx| {
13019        editor.move_left(&MoveLeft, window, cx);
13020    });
13021    cx.run_until_parked();
13022    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13023    cx.assert_editor_state("obj.aˇb");
13024    cx.update_editor(|editor, _, _| {
13025        assert_eq!(editor.context_menu_visible(), false);
13026    });
13027
13028    // Type "b" - new request
13029    cx.simulate_keystroke("b");
13030    let is_incomplete = false;
13031    handle_completion_request(
13032        "obj.<ab|>a",
13033        vec!["ab", "abc"],
13034        is_incomplete,
13035        counter.clone(),
13036        &mut cx,
13037    )
13038    .await;
13039    cx.run_until_parked();
13040    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13041    cx.assert_editor_state("obj.abˇb");
13042    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13043
13044    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13045    cx.update_editor(|editor, window, cx| {
13046        editor.backspace(&Backspace, window, cx);
13047    });
13048    let is_incomplete = false;
13049    handle_completion_request(
13050        "obj.<a|>b",
13051        vec!["a", "ab", "abc"],
13052        is_incomplete,
13053        counter.clone(),
13054        &mut cx,
13055    )
13056    .await;
13057    cx.run_until_parked();
13058    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13059    cx.assert_editor_state("obj.aˇb");
13060    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13061
13062    // Backspace to delete "a" - dismisses menu.
13063    cx.update_editor(|editor, window, cx| {
13064        editor.backspace(&Backspace, window, cx);
13065    });
13066    cx.run_until_parked();
13067    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13068    cx.assert_editor_state("obj.ˇb");
13069    cx.update_editor(|editor, _, _| {
13070        assert_eq!(editor.context_menu_visible(), false);
13071    });
13072}
13073
13074#[gpui::test]
13075async fn test_word_completion(cx: &mut TestAppContext) {
13076    let lsp_fetch_timeout_ms = 10;
13077    init_test(cx, |language_settings| {
13078        language_settings.defaults.completions = Some(CompletionSettings {
13079            words: WordsCompletionMode::Fallback,
13080            words_min_length: 0,
13081            lsp: true,
13082            lsp_fetch_timeout_ms: 10,
13083            lsp_insert_mode: LspInsertMode::Insert,
13084        });
13085    });
13086
13087    let mut cx = EditorLspTestContext::new_rust(
13088        lsp::ServerCapabilities {
13089            completion_provider: Some(lsp::CompletionOptions {
13090                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13091                ..lsp::CompletionOptions::default()
13092            }),
13093            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13094            ..lsp::ServerCapabilities::default()
13095        },
13096        cx,
13097    )
13098    .await;
13099
13100    let throttle_completions = Arc::new(AtomicBool::new(false));
13101
13102    let lsp_throttle_completions = throttle_completions.clone();
13103    let _completion_requests_handler =
13104        cx.lsp
13105            .server
13106            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13107                let lsp_throttle_completions = lsp_throttle_completions.clone();
13108                let cx = cx.clone();
13109                async move {
13110                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13111                        cx.background_executor()
13112                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13113                            .await;
13114                    }
13115                    Ok(Some(lsp::CompletionResponse::Array(vec![
13116                        lsp::CompletionItem {
13117                            label: "first".into(),
13118                            ..lsp::CompletionItem::default()
13119                        },
13120                        lsp::CompletionItem {
13121                            label: "last".into(),
13122                            ..lsp::CompletionItem::default()
13123                        },
13124                    ])))
13125                }
13126            });
13127
13128    cx.set_state(indoc! {"
13129        oneˇ
13130        two
13131        three
13132    "});
13133    cx.simulate_keystroke(".");
13134    cx.executor().run_until_parked();
13135    cx.condition(|editor, _| editor.context_menu_visible())
13136        .await;
13137    cx.update_editor(|editor, window, cx| {
13138        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13139        {
13140            assert_eq!(
13141                completion_menu_entries(menu),
13142                &["first", "last"],
13143                "When LSP server is fast to reply, no fallback word completions are used"
13144            );
13145        } else {
13146            panic!("expected completion menu to be open");
13147        }
13148        editor.cancel(&Cancel, window, cx);
13149    });
13150    cx.executor().run_until_parked();
13151    cx.condition(|editor, _| !editor.context_menu_visible())
13152        .await;
13153
13154    throttle_completions.store(true, atomic::Ordering::Release);
13155    cx.simulate_keystroke(".");
13156    cx.executor()
13157        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13158    cx.executor().run_until_parked();
13159    cx.condition(|editor, _| editor.context_menu_visible())
13160        .await;
13161    cx.update_editor(|editor, _, _| {
13162        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13163        {
13164            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13165                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13166        } else {
13167            panic!("expected completion menu to be open");
13168        }
13169    });
13170}
13171
13172#[gpui::test]
13173async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13174    init_test(cx, |language_settings| {
13175        language_settings.defaults.completions = Some(CompletionSettings {
13176            words: WordsCompletionMode::Enabled,
13177            words_min_length: 0,
13178            lsp: true,
13179            lsp_fetch_timeout_ms: 0,
13180            lsp_insert_mode: LspInsertMode::Insert,
13181        });
13182    });
13183
13184    let mut cx = EditorLspTestContext::new_rust(
13185        lsp::ServerCapabilities {
13186            completion_provider: Some(lsp::CompletionOptions {
13187                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13188                ..lsp::CompletionOptions::default()
13189            }),
13190            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13191            ..lsp::ServerCapabilities::default()
13192        },
13193        cx,
13194    )
13195    .await;
13196
13197    let _completion_requests_handler =
13198        cx.lsp
13199            .server
13200            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13201                Ok(Some(lsp::CompletionResponse::Array(vec![
13202                    lsp::CompletionItem {
13203                        label: "first".into(),
13204                        ..lsp::CompletionItem::default()
13205                    },
13206                    lsp::CompletionItem {
13207                        label: "last".into(),
13208                        ..lsp::CompletionItem::default()
13209                    },
13210                ])))
13211            });
13212
13213    cx.set_state(indoc! {"ˇ
13214        first
13215        last
13216        second
13217    "});
13218    cx.simulate_keystroke(".");
13219    cx.executor().run_until_parked();
13220    cx.condition(|editor, _| editor.context_menu_visible())
13221        .await;
13222    cx.update_editor(|editor, _, _| {
13223        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13224        {
13225            assert_eq!(
13226                completion_menu_entries(menu),
13227                &["first", "last", "second"],
13228                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13229            );
13230        } else {
13231            panic!("expected completion menu to be open");
13232        }
13233    });
13234}
13235
13236#[gpui::test]
13237async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13238    init_test(cx, |language_settings| {
13239        language_settings.defaults.completions = Some(CompletionSettings {
13240            words: WordsCompletionMode::Disabled,
13241            words_min_length: 0,
13242            lsp: true,
13243            lsp_fetch_timeout_ms: 0,
13244            lsp_insert_mode: LspInsertMode::Insert,
13245        });
13246    });
13247
13248    let mut cx = EditorLspTestContext::new_rust(
13249        lsp::ServerCapabilities {
13250            completion_provider: Some(lsp::CompletionOptions {
13251                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13252                ..lsp::CompletionOptions::default()
13253            }),
13254            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13255            ..lsp::ServerCapabilities::default()
13256        },
13257        cx,
13258    )
13259    .await;
13260
13261    let _completion_requests_handler =
13262        cx.lsp
13263            .server
13264            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13265                panic!("LSP completions should not be queried when dealing with word completions")
13266            });
13267
13268    cx.set_state(indoc! {"ˇ
13269        first
13270        last
13271        second
13272    "});
13273    cx.update_editor(|editor, window, cx| {
13274        editor.show_word_completions(&ShowWordCompletions, window, cx);
13275    });
13276    cx.executor().run_until_parked();
13277    cx.condition(|editor, _| editor.context_menu_visible())
13278        .await;
13279    cx.update_editor(|editor, _, _| {
13280        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13281        {
13282            assert_eq!(
13283                completion_menu_entries(menu),
13284                &["first", "last", "second"],
13285                "`ShowWordCompletions` action should show word completions"
13286            );
13287        } else {
13288            panic!("expected completion menu to be open");
13289        }
13290    });
13291
13292    cx.simulate_keystroke("l");
13293    cx.executor().run_until_parked();
13294    cx.condition(|editor, _| editor.context_menu_visible())
13295        .await;
13296    cx.update_editor(|editor, _, _| {
13297        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13298        {
13299            assert_eq!(
13300                completion_menu_entries(menu),
13301                &["last"],
13302                "After showing word completions, further editing should filter them and not query the LSP"
13303            );
13304        } else {
13305            panic!("expected completion menu to be open");
13306        }
13307    });
13308}
13309
13310#[gpui::test]
13311async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13312    init_test(cx, |language_settings| {
13313        language_settings.defaults.completions = Some(CompletionSettings {
13314            words: WordsCompletionMode::Fallback,
13315            words_min_length: 0,
13316            lsp: false,
13317            lsp_fetch_timeout_ms: 0,
13318            lsp_insert_mode: LspInsertMode::Insert,
13319        });
13320    });
13321
13322    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13323
13324    cx.set_state(indoc! {"ˇ
13325        0_usize
13326        let
13327        33
13328        4.5f32
13329    "});
13330    cx.update_editor(|editor, window, cx| {
13331        editor.show_completions(&ShowCompletions::default(), window, cx);
13332    });
13333    cx.executor().run_until_parked();
13334    cx.condition(|editor, _| editor.context_menu_visible())
13335        .await;
13336    cx.update_editor(|editor, window, cx| {
13337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13338        {
13339            assert_eq!(
13340                completion_menu_entries(menu),
13341                &["let"],
13342                "With no digits in the completion query, no digits should be in the word completions"
13343            );
13344        } else {
13345            panic!("expected completion menu to be open");
13346        }
13347        editor.cancel(&Cancel, window, cx);
13348    });
13349
13350    cx.set_state(indoc! {"13351        0_usize
13352        let
13353        3
13354        33.35f32
13355    "});
13356    cx.update_editor(|editor, window, cx| {
13357        editor.show_completions(&ShowCompletions::default(), window, cx);
13358    });
13359    cx.executor().run_until_parked();
13360    cx.condition(|editor, _| editor.context_menu_visible())
13361        .await;
13362    cx.update_editor(|editor, _, _| {
13363        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13364        {
13365            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13366                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13367        } else {
13368            panic!("expected completion menu to be open");
13369        }
13370    });
13371}
13372
13373#[gpui::test]
13374async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
13375    init_test(cx, |language_settings| {
13376        language_settings.defaults.completions = Some(CompletionSettings {
13377            words: WordsCompletionMode::Enabled,
13378            words_min_length: 3,
13379            lsp: true,
13380            lsp_fetch_timeout_ms: 0,
13381            lsp_insert_mode: LspInsertMode::Insert,
13382        });
13383    });
13384
13385    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13386    cx.set_state(indoc! {"ˇ
13387        wow
13388        wowen
13389        wowser
13390    "});
13391    cx.simulate_keystroke("w");
13392    cx.executor().run_until_parked();
13393    cx.update_editor(|editor, _, _| {
13394        if editor.context_menu.borrow_mut().is_some() {
13395            panic!(
13396                "expected completion menu to be hidden, as words completion threshold is not met"
13397            );
13398        }
13399    });
13400
13401    cx.simulate_keystroke("o");
13402    cx.executor().run_until_parked();
13403    cx.update_editor(|editor, _, _| {
13404        if editor.context_menu.borrow_mut().is_some() {
13405            panic!(
13406                "expected completion menu to be hidden, as words completion threshold is not met still"
13407            );
13408        }
13409    });
13410
13411    cx.simulate_keystroke("w");
13412    cx.executor().run_until_parked();
13413    cx.update_editor(|editor, _, _| {
13414        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13415        {
13416            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
13417        } else {
13418            panic!("expected completion menu to be open after the word completions threshold is met");
13419        }
13420    });
13421}
13422
13423fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13424    let position = || lsp::Position {
13425        line: params.text_document_position.position.line,
13426        character: params.text_document_position.position.character,
13427    };
13428    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13429        range: lsp::Range {
13430            start: position(),
13431            end: position(),
13432        },
13433        new_text: text.to_string(),
13434    }))
13435}
13436
13437#[gpui::test]
13438async fn test_multiline_completion(cx: &mut TestAppContext) {
13439    init_test(cx, |_| {});
13440
13441    let fs = FakeFs::new(cx.executor());
13442    fs.insert_tree(
13443        path!("/a"),
13444        json!({
13445            "main.ts": "a",
13446        }),
13447    )
13448    .await;
13449
13450    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13451    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13452    let typescript_language = Arc::new(Language::new(
13453        LanguageConfig {
13454            name: "TypeScript".into(),
13455            matcher: LanguageMatcher {
13456                path_suffixes: vec!["ts".to_string()],
13457                ..LanguageMatcher::default()
13458            },
13459            line_comments: vec!["// ".into()],
13460            ..LanguageConfig::default()
13461        },
13462        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13463    ));
13464    language_registry.add(typescript_language.clone());
13465    let mut fake_servers = language_registry.register_fake_lsp(
13466        "TypeScript",
13467        FakeLspAdapter {
13468            capabilities: lsp::ServerCapabilities {
13469                completion_provider: Some(lsp::CompletionOptions {
13470                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13471                    ..lsp::CompletionOptions::default()
13472                }),
13473                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13474                ..lsp::ServerCapabilities::default()
13475            },
13476            // Emulate vtsls label generation
13477            label_for_completion: Some(Box::new(|item, _| {
13478                let text = if let Some(description) = item
13479                    .label_details
13480                    .as_ref()
13481                    .and_then(|label_details| label_details.description.as_ref())
13482                {
13483                    format!("{} {}", item.label, description)
13484                } else if let Some(detail) = &item.detail {
13485                    format!("{} {}", item.label, detail)
13486                } else {
13487                    item.label.clone()
13488                };
13489                let len = text.len();
13490                Some(language::CodeLabel {
13491                    text,
13492                    runs: Vec::new(),
13493                    filter_range: 0..len,
13494                })
13495            })),
13496            ..FakeLspAdapter::default()
13497        },
13498    );
13499    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13500    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13501    let worktree_id = workspace
13502        .update(cx, |workspace, _window, cx| {
13503            workspace.project().update(cx, |project, cx| {
13504                project.worktrees(cx).next().unwrap().read(cx).id()
13505            })
13506        })
13507        .unwrap();
13508    let _buffer = project
13509        .update(cx, |project, cx| {
13510            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13511        })
13512        .await
13513        .unwrap();
13514    let editor = workspace
13515        .update(cx, |workspace, window, cx| {
13516            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13517        })
13518        .unwrap()
13519        .await
13520        .unwrap()
13521        .downcast::<Editor>()
13522        .unwrap();
13523    let fake_server = fake_servers.next().await.unwrap();
13524
13525    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13526    let multiline_label_2 = "a\nb\nc\n";
13527    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13528    let multiline_description = "d\ne\nf\n";
13529    let multiline_detail_2 = "g\nh\ni\n";
13530
13531    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13532        move |params, _| async move {
13533            Ok(Some(lsp::CompletionResponse::Array(vec![
13534                lsp::CompletionItem {
13535                    label: multiline_label.to_string(),
13536                    text_edit: gen_text_edit(&params, "new_text_1"),
13537                    ..lsp::CompletionItem::default()
13538                },
13539                lsp::CompletionItem {
13540                    label: "single line label 1".to_string(),
13541                    detail: Some(multiline_detail.to_string()),
13542                    text_edit: gen_text_edit(&params, "new_text_2"),
13543                    ..lsp::CompletionItem::default()
13544                },
13545                lsp::CompletionItem {
13546                    label: "single line label 2".to_string(),
13547                    label_details: Some(lsp::CompletionItemLabelDetails {
13548                        description: Some(multiline_description.to_string()),
13549                        detail: None,
13550                    }),
13551                    text_edit: gen_text_edit(&params, "new_text_2"),
13552                    ..lsp::CompletionItem::default()
13553                },
13554                lsp::CompletionItem {
13555                    label: multiline_label_2.to_string(),
13556                    detail: Some(multiline_detail_2.to_string()),
13557                    text_edit: gen_text_edit(&params, "new_text_3"),
13558                    ..lsp::CompletionItem::default()
13559                },
13560                lsp::CompletionItem {
13561                    label: "Label with many     spaces and \t but without newlines".to_string(),
13562                    detail: Some(
13563                        "Details with many     spaces and \t but without newlines".to_string(),
13564                    ),
13565                    text_edit: gen_text_edit(&params, "new_text_4"),
13566                    ..lsp::CompletionItem::default()
13567                },
13568            ])))
13569        },
13570    );
13571
13572    editor.update_in(cx, |editor, window, cx| {
13573        cx.focus_self(window);
13574        editor.move_to_end(&MoveToEnd, window, cx);
13575        editor.handle_input(".", window, cx);
13576    });
13577    cx.run_until_parked();
13578    completion_handle.next().await.unwrap();
13579
13580    editor.update(cx, |editor, _| {
13581        assert!(editor.context_menu_visible());
13582        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13583        {
13584            let completion_labels = menu
13585                .completions
13586                .borrow()
13587                .iter()
13588                .map(|c| c.label.text.clone())
13589                .collect::<Vec<_>>();
13590            assert_eq!(
13591                completion_labels,
13592                &[
13593                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13594                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13595                    "single line label 2 d e f ",
13596                    "a b c g h i ",
13597                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13598                ],
13599                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13600            );
13601
13602            for completion in menu
13603                .completions
13604                .borrow()
13605                .iter() {
13606                    assert_eq!(
13607                        completion.label.filter_range,
13608                        0..completion.label.text.len(),
13609                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13610                    );
13611                }
13612        } else {
13613            panic!("expected completion menu to be open");
13614        }
13615    });
13616}
13617
13618#[gpui::test]
13619async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13620    init_test(cx, |_| {});
13621    let mut cx = EditorLspTestContext::new_rust(
13622        lsp::ServerCapabilities {
13623            completion_provider: Some(lsp::CompletionOptions {
13624                trigger_characters: Some(vec![".".to_string()]),
13625                ..Default::default()
13626            }),
13627            ..Default::default()
13628        },
13629        cx,
13630    )
13631    .await;
13632    cx.lsp
13633        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13634            Ok(Some(lsp::CompletionResponse::Array(vec![
13635                lsp::CompletionItem {
13636                    label: "first".into(),
13637                    ..Default::default()
13638                },
13639                lsp::CompletionItem {
13640                    label: "last".into(),
13641                    ..Default::default()
13642                },
13643            ])))
13644        });
13645    cx.set_state("variableˇ");
13646    cx.simulate_keystroke(".");
13647    cx.executor().run_until_parked();
13648
13649    cx.update_editor(|editor, _, _| {
13650        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13651        {
13652            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
13653        } else {
13654            panic!("expected completion menu to be open");
13655        }
13656    });
13657
13658    cx.update_editor(|editor, window, cx| {
13659        editor.move_page_down(&MovePageDown::default(), window, cx);
13660        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13661        {
13662            assert!(
13663                menu.selected_item == 1,
13664                "expected PageDown to select the last item from the context menu"
13665            );
13666        } else {
13667            panic!("expected completion menu to stay open after PageDown");
13668        }
13669    });
13670
13671    cx.update_editor(|editor, window, cx| {
13672        editor.move_page_up(&MovePageUp::default(), window, cx);
13673        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13674        {
13675            assert!(
13676                menu.selected_item == 0,
13677                "expected PageUp to select the first item from the context menu"
13678            );
13679        } else {
13680            panic!("expected completion menu to stay open after PageUp");
13681        }
13682    });
13683}
13684
13685#[gpui::test]
13686async fn test_as_is_completions(cx: &mut TestAppContext) {
13687    init_test(cx, |_| {});
13688    let mut cx = EditorLspTestContext::new_rust(
13689        lsp::ServerCapabilities {
13690            completion_provider: Some(lsp::CompletionOptions {
13691                ..Default::default()
13692            }),
13693            ..Default::default()
13694        },
13695        cx,
13696    )
13697    .await;
13698    cx.lsp
13699        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13700            Ok(Some(lsp::CompletionResponse::Array(vec![
13701                lsp::CompletionItem {
13702                    label: "unsafe".into(),
13703                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13704                        range: lsp::Range {
13705                            start: lsp::Position {
13706                                line: 1,
13707                                character: 2,
13708                            },
13709                            end: lsp::Position {
13710                                line: 1,
13711                                character: 3,
13712                            },
13713                        },
13714                        new_text: "unsafe".to_string(),
13715                    })),
13716                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13717                    ..Default::default()
13718                },
13719            ])))
13720        });
13721    cx.set_state("fn a() {}\n");
13722    cx.executor().run_until_parked();
13723    cx.update_editor(|editor, window, cx| {
13724        editor.show_completions(
13725            &ShowCompletions {
13726                trigger: Some("\n".into()),
13727            },
13728            window,
13729            cx,
13730        );
13731    });
13732    cx.executor().run_until_parked();
13733
13734    cx.update_editor(|editor, window, cx| {
13735        editor.confirm_completion(&Default::default(), window, cx)
13736    });
13737    cx.executor().run_until_parked();
13738    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13739}
13740
13741#[gpui::test]
13742async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13743    init_test(cx, |_| {});
13744    let language =
13745        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13746    let mut cx = EditorLspTestContext::new(
13747        language,
13748        lsp::ServerCapabilities {
13749            completion_provider: Some(lsp::CompletionOptions {
13750                ..lsp::CompletionOptions::default()
13751            }),
13752            ..lsp::ServerCapabilities::default()
13753        },
13754        cx,
13755    )
13756    .await;
13757
13758    cx.set_state(
13759        "#ifndef BAR_H
13760#define BAR_H
13761
13762#include <stdbool.h>
13763
13764int fn_branch(bool do_branch1, bool do_branch2);
13765
13766#endif // BAR_H
13767ˇ",
13768    );
13769    cx.executor().run_until_parked();
13770    cx.update_editor(|editor, window, cx| {
13771        editor.handle_input("#", window, cx);
13772    });
13773    cx.executor().run_until_parked();
13774    cx.update_editor(|editor, window, cx| {
13775        editor.handle_input("i", window, cx);
13776    });
13777    cx.executor().run_until_parked();
13778    cx.update_editor(|editor, window, cx| {
13779        editor.handle_input("n", window, cx);
13780    });
13781    cx.executor().run_until_parked();
13782    cx.assert_editor_state(
13783        "#ifndef BAR_H
13784#define BAR_H
13785
13786#include <stdbool.h>
13787
13788int fn_branch(bool do_branch1, bool do_branch2);
13789
13790#endif // BAR_H
13791#inˇ",
13792    );
13793
13794    cx.lsp
13795        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13796            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13797                is_incomplete: false,
13798                item_defaults: None,
13799                items: vec![lsp::CompletionItem {
13800                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13801                    label_details: Some(lsp::CompletionItemLabelDetails {
13802                        detail: Some("header".to_string()),
13803                        description: None,
13804                    }),
13805                    label: " include".to_string(),
13806                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13807                        range: lsp::Range {
13808                            start: lsp::Position {
13809                                line: 8,
13810                                character: 1,
13811                            },
13812                            end: lsp::Position {
13813                                line: 8,
13814                                character: 1,
13815                            },
13816                        },
13817                        new_text: "include \"$0\"".to_string(),
13818                    })),
13819                    sort_text: Some("40b67681include".to_string()),
13820                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13821                    filter_text: Some("include".to_string()),
13822                    insert_text: Some("include \"$0\"".to_string()),
13823                    ..lsp::CompletionItem::default()
13824                }],
13825            })))
13826        });
13827    cx.update_editor(|editor, window, cx| {
13828        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13829    });
13830    cx.executor().run_until_parked();
13831    cx.update_editor(|editor, window, cx| {
13832        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13833    });
13834    cx.executor().run_until_parked();
13835    cx.assert_editor_state(
13836        "#ifndef BAR_H
13837#define BAR_H
13838
13839#include <stdbool.h>
13840
13841int fn_branch(bool do_branch1, bool do_branch2);
13842
13843#endif // BAR_H
13844#include \"ˇ\"",
13845    );
13846
13847    cx.lsp
13848        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13849            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13850                is_incomplete: true,
13851                item_defaults: None,
13852                items: vec![lsp::CompletionItem {
13853                    kind: Some(lsp::CompletionItemKind::FILE),
13854                    label: "AGL/".to_string(),
13855                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13856                        range: lsp::Range {
13857                            start: lsp::Position {
13858                                line: 8,
13859                                character: 10,
13860                            },
13861                            end: lsp::Position {
13862                                line: 8,
13863                                character: 11,
13864                            },
13865                        },
13866                        new_text: "AGL/".to_string(),
13867                    })),
13868                    sort_text: Some("40b67681AGL/".to_string()),
13869                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13870                    filter_text: Some("AGL/".to_string()),
13871                    insert_text: Some("AGL/".to_string()),
13872                    ..lsp::CompletionItem::default()
13873                }],
13874            })))
13875        });
13876    cx.update_editor(|editor, window, cx| {
13877        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13878    });
13879    cx.executor().run_until_parked();
13880    cx.update_editor(|editor, window, cx| {
13881        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13882    });
13883    cx.executor().run_until_parked();
13884    cx.assert_editor_state(
13885        r##"#ifndef BAR_H
13886#define BAR_H
13887
13888#include <stdbool.h>
13889
13890int fn_branch(bool do_branch1, bool do_branch2);
13891
13892#endif // BAR_H
13893#include "AGL/ˇ"##,
13894    );
13895
13896    cx.update_editor(|editor, window, cx| {
13897        editor.handle_input("\"", window, cx);
13898    });
13899    cx.executor().run_until_parked();
13900    cx.assert_editor_state(
13901        r##"#ifndef BAR_H
13902#define BAR_H
13903
13904#include <stdbool.h>
13905
13906int fn_branch(bool do_branch1, bool do_branch2);
13907
13908#endif // BAR_H
13909#include "AGL/"ˇ"##,
13910    );
13911}
13912
13913#[gpui::test]
13914async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13915    init_test(cx, |_| {});
13916
13917    let mut cx = EditorLspTestContext::new_rust(
13918        lsp::ServerCapabilities {
13919            completion_provider: Some(lsp::CompletionOptions {
13920                trigger_characters: Some(vec![".".to_string()]),
13921                resolve_provider: Some(true),
13922                ..Default::default()
13923            }),
13924            ..Default::default()
13925        },
13926        cx,
13927    )
13928    .await;
13929
13930    cx.set_state("fn main() { let a = 2ˇ; }");
13931    cx.simulate_keystroke(".");
13932    let completion_item = lsp::CompletionItem {
13933        label: "Some".into(),
13934        kind: Some(lsp::CompletionItemKind::SNIPPET),
13935        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13936        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13937            kind: lsp::MarkupKind::Markdown,
13938            value: "```rust\nSome(2)\n```".to_string(),
13939        })),
13940        deprecated: Some(false),
13941        sort_text: Some("Some".to_string()),
13942        filter_text: Some("Some".to_string()),
13943        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13944        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13945            range: lsp::Range {
13946                start: lsp::Position {
13947                    line: 0,
13948                    character: 22,
13949                },
13950                end: lsp::Position {
13951                    line: 0,
13952                    character: 22,
13953                },
13954            },
13955            new_text: "Some(2)".to_string(),
13956        })),
13957        additional_text_edits: Some(vec![lsp::TextEdit {
13958            range: lsp::Range {
13959                start: lsp::Position {
13960                    line: 0,
13961                    character: 20,
13962                },
13963                end: lsp::Position {
13964                    line: 0,
13965                    character: 22,
13966                },
13967            },
13968            new_text: "".to_string(),
13969        }]),
13970        ..Default::default()
13971    };
13972
13973    let closure_completion_item = completion_item.clone();
13974    let counter = Arc::new(AtomicUsize::new(0));
13975    let counter_clone = counter.clone();
13976    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13977        let task_completion_item = closure_completion_item.clone();
13978        counter_clone.fetch_add(1, atomic::Ordering::Release);
13979        async move {
13980            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13981                is_incomplete: true,
13982                item_defaults: None,
13983                items: vec![task_completion_item],
13984            })))
13985        }
13986    });
13987
13988    cx.condition(|editor, _| editor.context_menu_visible())
13989        .await;
13990    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13991    assert!(request.next().await.is_some());
13992    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13993
13994    cx.simulate_keystrokes("S o m");
13995    cx.condition(|editor, _| editor.context_menu_visible())
13996        .await;
13997    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13998    assert!(request.next().await.is_some());
13999    assert!(request.next().await.is_some());
14000    assert!(request.next().await.is_some());
14001    request.close();
14002    assert!(request.next().await.is_none());
14003    assert_eq!(
14004        counter.load(atomic::Ordering::Acquire),
14005        4,
14006        "With the completions menu open, only one LSP request should happen per input"
14007    );
14008}
14009
14010#[gpui::test]
14011async fn test_toggle_comment(cx: &mut TestAppContext) {
14012    init_test(cx, |_| {});
14013    let mut cx = EditorTestContext::new(cx).await;
14014    let language = Arc::new(Language::new(
14015        LanguageConfig {
14016            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14017            ..Default::default()
14018        },
14019        Some(tree_sitter_rust::LANGUAGE.into()),
14020    ));
14021    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14022
14023    // If multiple selections intersect a line, the line is only toggled once.
14024    cx.set_state(indoc! {"
14025        fn a() {
14026            «//b();
14027            ˇ»// «c();
14028            //ˇ»  d();
14029        }
14030    "});
14031
14032    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14033
14034    cx.assert_editor_state(indoc! {"
14035        fn a() {
14036            «b();
14037            c();
14038            ˇ» d();
14039        }
14040    "});
14041
14042    // The comment prefix is inserted at the same column for every line in a
14043    // selection.
14044    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14045
14046    cx.assert_editor_state(indoc! {"
14047        fn a() {
14048            // «b();
14049            // c();
14050            ˇ»//  d();
14051        }
14052    "});
14053
14054    // If a selection ends at the beginning of a line, that line is not toggled.
14055    cx.set_selections_state(indoc! {"
14056        fn a() {
14057            // b();
14058            «// c();
14059        ˇ»    //  d();
14060        }
14061    "});
14062
14063    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14064
14065    cx.assert_editor_state(indoc! {"
14066        fn a() {
14067            // b();
14068            «c();
14069        ˇ»    //  d();
14070        }
14071    "});
14072
14073    // If a selection span a single line and is empty, the line is toggled.
14074    cx.set_state(indoc! {"
14075        fn a() {
14076            a();
14077            b();
14078        ˇ
14079        }
14080    "});
14081
14082    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14083
14084    cx.assert_editor_state(indoc! {"
14085        fn a() {
14086            a();
14087            b();
14088        //•ˇ
14089        }
14090    "});
14091
14092    // If a selection span multiple lines, empty lines are not toggled.
14093    cx.set_state(indoc! {"
14094        fn a() {
14095            «a();
14096
14097            c();ˇ»
14098        }
14099    "});
14100
14101    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14102
14103    cx.assert_editor_state(indoc! {"
14104        fn a() {
14105            // «a();
14106
14107            // c();ˇ»
14108        }
14109    "});
14110
14111    // If a selection includes multiple comment prefixes, all lines are uncommented.
14112    cx.set_state(indoc! {"
14113        fn a() {
14114            «// a();
14115            /// b();
14116            //! c();ˇ»
14117        }
14118    "});
14119
14120    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14121
14122    cx.assert_editor_state(indoc! {"
14123        fn a() {
14124            «a();
14125            b();
14126            c();ˇ»
14127        }
14128    "});
14129}
14130
14131#[gpui::test]
14132async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14133    init_test(cx, |_| {});
14134    let mut cx = EditorTestContext::new(cx).await;
14135    let language = Arc::new(Language::new(
14136        LanguageConfig {
14137            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14138            ..Default::default()
14139        },
14140        Some(tree_sitter_rust::LANGUAGE.into()),
14141    ));
14142    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14143
14144    let toggle_comments = &ToggleComments {
14145        advance_downwards: false,
14146        ignore_indent: true,
14147    };
14148
14149    // If multiple selections intersect a line, the line is only toggled once.
14150    cx.set_state(indoc! {"
14151        fn a() {
14152        //    «b();
14153        //    c();
14154        //    ˇ» d();
14155        }
14156    "});
14157
14158    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14159
14160    cx.assert_editor_state(indoc! {"
14161        fn a() {
14162            «b();
14163            c();
14164            ˇ» d();
14165        }
14166    "});
14167
14168    // The comment prefix is inserted at the beginning of each line
14169    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14170
14171    cx.assert_editor_state(indoc! {"
14172        fn a() {
14173        //    «b();
14174        //    c();
14175        //    ˇ» d();
14176        }
14177    "});
14178
14179    // If a selection ends at the beginning of a line, that line is not toggled.
14180    cx.set_selections_state(indoc! {"
14181        fn a() {
14182        //    b();
14183        //    «c();
14184        ˇ»//     d();
14185        }
14186    "});
14187
14188    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14189
14190    cx.assert_editor_state(indoc! {"
14191        fn a() {
14192        //    b();
14193            «c();
14194        ˇ»//     d();
14195        }
14196    "});
14197
14198    // If a selection span a single line and is empty, the line is toggled.
14199    cx.set_state(indoc! {"
14200        fn a() {
14201            a();
14202            b();
14203        ˇ
14204        }
14205    "});
14206
14207    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14208
14209    cx.assert_editor_state(indoc! {"
14210        fn a() {
14211            a();
14212            b();
14213        //ˇ
14214        }
14215    "});
14216
14217    // If a selection span multiple lines, empty lines are not toggled.
14218    cx.set_state(indoc! {"
14219        fn a() {
14220            «a();
14221
14222            c();ˇ»
14223        }
14224    "});
14225
14226    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14227
14228    cx.assert_editor_state(indoc! {"
14229        fn a() {
14230        //    «a();
14231
14232        //    c();ˇ»
14233        }
14234    "});
14235
14236    // If a selection includes multiple comment prefixes, all lines are uncommented.
14237    cx.set_state(indoc! {"
14238        fn a() {
14239        //    «a();
14240        ///    b();
14241        //!    c();ˇ»
14242        }
14243    "});
14244
14245    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14246
14247    cx.assert_editor_state(indoc! {"
14248        fn a() {
14249            «a();
14250            b();
14251            c();ˇ»
14252        }
14253    "});
14254}
14255
14256#[gpui::test]
14257async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14258    init_test(cx, |_| {});
14259
14260    let language = Arc::new(Language::new(
14261        LanguageConfig {
14262            line_comments: vec!["// ".into()],
14263            ..Default::default()
14264        },
14265        Some(tree_sitter_rust::LANGUAGE.into()),
14266    ));
14267
14268    let mut cx = EditorTestContext::new(cx).await;
14269
14270    cx.language_registry().add(language.clone());
14271    cx.update_buffer(|buffer, cx| {
14272        buffer.set_language(Some(language), cx);
14273    });
14274
14275    let toggle_comments = &ToggleComments {
14276        advance_downwards: true,
14277        ignore_indent: false,
14278    };
14279
14280    // Single cursor on one line -> advance
14281    // Cursor moves horizontally 3 characters as well on non-blank line
14282    cx.set_state(indoc!(
14283        "fn a() {
14284             ˇdog();
14285             cat();
14286        }"
14287    ));
14288    cx.update_editor(|editor, window, cx| {
14289        editor.toggle_comments(toggle_comments, window, cx);
14290    });
14291    cx.assert_editor_state(indoc!(
14292        "fn a() {
14293             // dog();
14294             catˇ();
14295        }"
14296    ));
14297
14298    // Single selection on one line -> don't advance
14299    cx.set_state(indoc!(
14300        "fn a() {
14301             «dog()ˇ»;
14302             cat();
14303        }"
14304    ));
14305    cx.update_editor(|editor, window, cx| {
14306        editor.toggle_comments(toggle_comments, window, cx);
14307    });
14308    cx.assert_editor_state(indoc!(
14309        "fn a() {
14310             // «dog()ˇ»;
14311             cat();
14312        }"
14313    ));
14314
14315    // Multiple cursors on one line -> advance
14316    cx.set_state(indoc!(
14317        "fn a() {
14318             ˇdˇog();
14319             cat();
14320        }"
14321    ));
14322    cx.update_editor(|editor, window, cx| {
14323        editor.toggle_comments(toggle_comments, window, cx);
14324    });
14325    cx.assert_editor_state(indoc!(
14326        "fn a() {
14327             // dog();
14328             catˇ(ˇ);
14329        }"
14330    ));
14331
14332    // Multiple cursors on one line, with selection -> don't advance
14333    cx.set_state(indoc!(
14334        "fn a() {
14335             ˇdˇog«()ˇ»;
14336             cat();
14337        }"
14338    ));
14339    cx.update_editor(|editor, window, cx| {
14340        editor.toggle_comments(toggle_comments, window, cx);
14341    });
14342    cx.assert_editor_state(indoc!(
14343        "fn a() {
14344             // ˇdˇog«()ˇ»;
14345             cat();
14346        }"
14347    ));
14348
14349    // Single cursor on one line -> advance
14350    // Cursor moves to column 0 on blank line
14351    cx.set_state(indoc!(
14352        "fn a() {
14353             ˇdog();
14354
14355             cat();
14356        }"
14357    ));
14358    cx.update_editor(|editor, window, cx| {
14359        editor.toggle_comments(toggle_comments, window, cx);
14360    });
14361    cx.assert_editor_state(indoc!(
14362        "fn a() {
14363             // dog();
14364        ˇ
14365             cat();
14366        }"
14367    ));
14368
14369    // Single cursor on one line -> advance
14370    // Cursor starts and ends at column 0
14371    cx.set_state(indoc!(
14372        "fn a() {
14373         ˇ    dog();
14374             cat();
14375        }"
14376    ));
14377    cx.update_editor(|editor, window, cx| {
14378        editor.toggle_comments(toggle_comments, window, cx);
14379    });
14380    cx.assert_editor_state(indoc!(
14381        "fn a() {
14382             // dog();
14383         ˇ    cat();
14384        }"
14385    ));
14386}
14387
14388#[gpui::test]
14389async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14390    init_test(cx, |_| {});
14391
14392    let mut cx = EditorTestContext::new(cx).await;
14393
14394    let html_language = Arc::new(
14395        Language::new(
14396            LanguageConfig {
14397                name: "HTML".into(),
14398                block_comment: Some(BlockCommentConfig {
14399                    start: "<!-- ".into(),
14400                    prefix: "".into(),
14401                    end: " -->".into(),
14402                    tab_size: 0,
14403                }),
14404                ..Default::default()
14405            },
14406            Some(tree_sitter_html::LANGUAGE.into()),
14407        )
14408        .with_injection_query(
14409            r#"
14410            (script_element
14411                (raw_text) @injection.content
14412                (#set! injection.language "javascript"))
14413            "#,
14414        )
14415        .unwrap(),
14416    );
14417
14418    let javascript_language = Arc::new(Language::new(
14419        LanguageConfig {
14420            name: "JavaScript".into(),
14421            line_comments: vec!["// ".into()],
14422            ..Default::default()
14423        },
14424        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14425    ));
14426
14427    cx.language_registry().add(html_language.clone());
14428    cx.language_registry().add(javascript_language);
14429    cx.update_buffer(|buffer, cx| {
14430        buffer.set_language(Some(html_language), cx);
14431    });
14432
14433    // Toggle comments for empty selections
14434    cx.set_state(
14435        &r#"
14436            <p>A</p>ˇ
14437            <p>B</p>ˇ
14438            <p>C</p>ˇ
14439        "#
14440        .unindent(),
14441    );
14442    cx.update_editor(|editor, window, cx| {
14443        editor.toggle_comments(&ToggleComments::default(), window, cx)
14444    });
14445    cx.assert_editor_state(
14446        &r#"
14447            <!-- <p>A</p>ˇ -->
14448            <!-- <p>B</p>ˇ -->
14449            <!-- <p>C</p>ˇ -->
14450        "#
14451        .unindent(),
14452    );
14453    cx.update_editor(|editor, window, cx| {
14454        editor.toggle_comments(&ToggleComments::default(), window, cx)
14455    });
14456    cx.assert_editor_state(
14457        &r#"
14458            <p>A</p>ˇ
14459            <p>B</p>ˇ
14460            <p>C</p>ˇ
14461        "#
14462        .unindent(),
14463    );
14464
14465    // Toggle comments for mixture of empty and non-empty selections, where
14466    // multiple selections occupy a given line.
14467    cx.set_state(
14468        &r#"
14469            <p>A«</p>
14470            <p>ˇ»B</p>ˇ
14471            <p>C«</p>
14472            <p>ˇ»D</p>ˇ
14473        "#
14474        .unindent(),
14475    );
14476
14477    cx.update_editor(|editor, window, cx| {
14478        editor.toggle_comments(&ToggleComments::default(), window, cx)
14479    });
14480    cx.assert_editor_state(
14481        &r#"
14482            <!-- <p>A«</p>
14483            <p>ˇ»B</p>ˇ -->
14484            <!-- <p>C«</p>
14485            <p>ˇ»D</p>ˇ -->
14486        "#
14487        .unindent(),
14488    );
14489    cx.update_editor(|editor, window, cx| {
14490        editor.toggle_comments(&ToggleComments::default(), window, cx)
14491    });
14492    cx.assert_editor_state(
14493        &r#"
14494            <p>A«</p>
14495            <p>ˇ»B</p>ˇ
14496            <p>C«</p>
14497            <p>ˇ»D</p>ˇ
14498        "#
14499        .unindent(),
14500    );
14501
14502    // Toggle comments when different languages are active for different
14503    // selections.
14504    cx.set_state(
14505        &r#"
14506            ˇ<script>
14507                ˇvar x = new Y();
14508            ˇ</script>
14509        "#
14510        .unindent(),
14511    );
14512    cx.executor().run_until_parked();
14513    cx.update_editor(|editor, window, cx| {
14514        editor.toggle_comments(&ToggleComments::default(), window, cx)
14515    });
14516    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14517    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14518    cx.assert_editor_state(
14519        &r#"
14520            <!-- ˇ<script> -->
14521                // ˇvar x = new Y();
14522            <!-- ˇ</script> -->
14523        "#
14524        .unindent(),
14525    );
14526}
14527
14528#[gpui::test]
14529fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14530    init_test(cx, |_| {});
14531
14532    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14533    let multibuffer = cx.new(|cx| {
14534        let mut multibuffer = MultiBuffer::new(ReadWrite);
14535        multibuffer.push_excerpts(
14536            buffer.clone(),
14537            [
14538                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14539                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14540            ],
14541            cx,
14542        );
14543        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14544        multibuffer
14545    });
14546
14547    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14548    editor.update_in(cx, |editor, window, cx| {
14549        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14551            s.select_ranges([
14552                Point::new(0, 0)..Point::new(0, 0),
14553                Point::new(1, 0)..Point::new(1, 0),
14554            ])
14555        });
14556
14557        editor.handle_input("X", window, cx);
14558        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14559        assert_eq!(
14560            editor.selections.ranges(cx),
14561            [
14562                Point::new(0, 1)..Point::new(0, 1),
14563                Point::new(1, 1)..Point::new(1, 1),
14564            ]
14565        );
14566
14567        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14569            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14570        });
14571        editor.backspace(&Default::default(), window, cx);
14572        assert_eq!(editor.text(cx), "Xa\nbbb");
14573        assert_eq!(
14574            editor.selections.ranges(cx),
14575            [Point::new(1, 0)..Point::new(1, 0)]
14576        );
14577
14578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14579            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14580        });
14581        editor.backspace(&Default::default(), window, cx);
14582        assert_eq!(editor.text(cx), "X\nbb");
14583        assert_eq!(
14584            editor.selections.ranges(cx),
14585            [Point::new(0, 1)..Point::new(0, 1)]
14586        );
14587    });
14588}
14589
14590#[gpui::test]
14591fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14592    init_test(cx, |_| {});
14593
14594    let markers = vec![('[', ']').into(), ('(', ')').into()];
14595    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14596        indoc! {"
14597            [aaaa
14598            (bbbb]
14599            cccc)",
14600        },
14601        markers.clone(),
14602    );
14603    let excerpt_ranges = markers.into_iter().map(|marker| {
14604        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14605        ExcerptRange::new(context)
14606    });
14607    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14608    let multibuffer = cx.new(|cx| {
14609        let mut multibuffer = MultiBuffer::new(ReadWrite);
14610        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14611        multibuffer
14612    });
14613
14614    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14615    editor.update_in(cx, |editor, window, cx| {
14616        let (expected_text, selection_ranges) = marked_text_ranges(
14617            indoc! {"
14618                aaaa
14619                bˇbbb
14620                bˇbbˇb
14621                cccc"
14622            },
14623            true,
14624        );
14625        assert_eq!(editor.text(cx), expected_text);
14626        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14627            s.select_ranges(selection_ranges)
14628        });
14629
14630        editor.handle_input("X", window, cx);
14631
14632        let (expected_text, expected_selections) = marked_text_ranges(
14633            indoc! {"
14634                aaaa
14635                bXˇbbXb
14636                bXˇbbXˇb
14637                cccc"
14638            },
14639            false,
14640        );
14641        assert_eq!(editor.text(cx), expected_text);
14642        assert_eq!(editor.selections.ranges(cx), expected_selections);
14643
14644        editor.newline(&Newline, window, cx);
14645        let (expected_text, expected_selections) = marked_text_ranges(
14646            indoc! {"
14647                aaaa
14648                bX
14649                ˇbbX
14650                b
14651                bX
14652                ˇbbX
14653                ˇb
14654                cccc"
14655            },
14656            false,
14657        );
14658        assert_eq!(editor.text(cx), expected_text);
14659        assert_eq!(editor.selections.ranges(cx), expected_selections);
14660    });
14661}
14662
14663#[gpui::test]
14664fn test_refresh_selections(cx: &mut TestAppContext) {
14665    init_test(cx, |_| {});
14666
14667    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14668    let mut excerpt1_id = None;
14669    let multibuffer = cx.new(|cx| {
14670        let mut multibuffer = MultiBuffer::new(ReadWrite);
14671        excerpt1_id = multibuffer
14672            .push_excerpts(
14673                buffer.clone(),
14674                [
14675                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14676                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14677                ],
14678                cx,
14679            )
14680            .into_iter()
14681            .next();
14682        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14683        multibuffer
14684    });
14685
14686    let editor = cx.add_window(|window, cx| {
14687        let mut editor = build_editor(multibuffer.clone(), window, cx);
14688        let snapshot = editor.snapshot(window, cx);
14689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14690            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14691        });
14692        editor.begin_selection(
14693            Point::new(2, 1).to_display_point(&snapshot),
14694            true,
14695            1,
14696            window,
14697            cx,
14698        );
14699        assert_eq!(
14700            editor.selections.ranges(cx),
14701            [
14702                Point::new(1, 3)..Point::new(1, 3),
14703                Point::new(2, 1)..Point::new(2, 1),
14704            ]
14705        );
14706        editor
14707    });
14708
14709    // Refreshing selections is a no-op when excerpts haven't changed.
14710    _ = editor.update(cx, |editor, window, cx| {
14711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14712        assert_eq!(
14713            editor.selections.ranges(cx),
14714            [
14715                Point::new(1, 3)..Point::new(1, 3),
14716                Point::new(2, 1)..Point::new(2, 1),
14717            ]
14718        );
14719    });
14720
14721    multibuffer.update(cx, |multibuffer, cx| {
14722        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14723    });
14724    _ = editor.update(cx, |editor, window, cx| {
14725        // Removing an excerpt causes the first selection to become degenerate.
14726        assert_eq!(
14727            editor.selections.ranges(cx),
14728            [
14729                Point::new(0, 0)..Point::new(0, 0),
14730                Point::new(0, 1)..Point::new(0, 1)
14731            ]
14732        );
14733
14734        // Refreshing selections will relocate the first selection to the original buffer
14735        // location.
14736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14737        assert_eq!(
14738            editor.selections.ranges(cx),
14739            [
14740                Point::new(0, 1)..Point::new(0, 1),
14741                Point::new(0, 3)..Point::new(0, 3)
14742            ]
14743        );
14744        assert!(editor.selections.pending_anchor().is_some());
14745    });
14746}
14747
14748#[gpui::test]
14749fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14750    init_test(cx, |_| {});
14751
14752    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14753    let mut excerpt1_id = None;
14754    let multibuffer = cx.new(|cx| {
14755        let mut multibuffer = MultiBuffer::new(ReadWrite);
14756        excerpt1_id = multibuffer
14757            .push_excerpts(
14758                buffer.clone(),
14759                [
14760                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14761                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14762                ],
14763                cx,
14764            )
14765            .into_iter()
14766            .next();
14767        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14768        multibuffer
14769    });
14770
14771    let editor = cx.add_window(|window, cx| {
14772        let mut editor = build_editor(multibuffer.clone(), window, cx);
14773        let snapshot = editor.snapshot(window, cx);
14774        editor.begin_selection(
14775            Point::new(1, 3).to_display_point(&snapshot),
14776            false,
14777            1,
14778            window,
14779            cx,
14780        );
14781        assert_eq!(
14782            editor.selections.ranges(cx),
14783            [Point::new(1, 3)..Point::new(1, 3)]
14784        );
14785        editor
14786    });
14787
14788    multibuffer.update(cx, |multibuffer, cx| {
14789        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14790    });
14791    _ = editor.update(cx, |editor, window, cx| {
14792        assert_eq!(
14793            editor.selections.ranges(cx),
14794            [Point::new(0, 0)..Point::new(0, 0)]
14795        );
14796
14797        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14799        assert_eq!(
14800            editor.selections.ranges(cx),
14801            [Point::new(0, 3)..Point::new(0, 3)]
14802        );
14803        assert!(editor.selections.pending_anchor().is_some());
14804    });
14805}
14806
14807#[gpui::test]
14808async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14809    init_test(cx, |_| {});
14810
14811    let language = Arc::new(
14812        Language::new(
14813            LanguageConfig {
14814                brackets: BracketPairConfig {
14815                    pairs: vec![
14816                        BracketPair {
14817                            start: "{".to_string(),
14818                            end: "}".to_string(),
14819                            close: true,
14820                            surround: true,
14821                            newline: true,
14822                        },
14823                        BracketPair {
14824                            start: "/* ".to_string(),
14825                            end: " */".to_string(),
14826                            close: true,
14827                            surround: true,
14828                            newline: true,
14829                        },
14830                    ],
14831                    ..Default::default()
14832                },
14833                ..Default::default()
14834            },
14835            Some(tree_sitter_rust::LANGUAGE.into()),
14836        )
14837        .with_indents_query("")
14838        .unwrap(),
14839    );
14840
14841    let text = concat!(
14842        "{   }\n",     //
14843        "  x\n",       //
14844        "  /*   */\n", //
14845        "x\n",         //
14846        "{{} }\n",     //
14847    );
14848
14849    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14850    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14851    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14852    editor
14853        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14854        .await;
14855
14856    editor.update_in(cx, |editor, window, cx| {
14857        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14858            s.select_display_ranges([
14859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14860                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14861                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14862            ])
14863        });
14864        editor.newline(&Newline, window, cx);
14865
14866        assert_eq!(
14867            editor.buffer().read(cx).read(cx).text(),
14868            concat!(
14869                "{ \n",    // Suppress rustfmt
14870                "\n",      //
14871                "}\n",     //
14872                "  x\n",   //
14873                "  /* \n", //
14874                "  \n",    //
14875                "  */\n",  //
14876                "x\n",     //
14877                "{{} \n",  //
14878                "}\n",     //
14879            )
14880        );
14881    });
14882}
14883
14884#[gpui::test]
14885fn test_highlighted_ranges(cx: &mut TestAppContext) {
14886    init_test(cx, |_| {});
14887
14888    let editor = cx.add_window(|window, cx| {
14889        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14890        build_editor(buffer, window, cx)
14891    });
14892
14893    _ = editor.update(cx, |editor, window, cx| {
14894        struct Type1;
14895        struct Type2;
14896
14897        let buffer = editor.buffer.read(cx).snapshot(cx);
14898
14899        let anchor_range =
14900            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14901
14902        editor.highlight_background::<Type1>(
14903            &[
14904                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14905                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14906                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14907                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14908            ],
14909            |_| Hsla::red(),
14910            cx,
14911        );
14912        editor.highlight_background::<Type2>(
14913            &[
14914                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14915                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14916                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14917                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14918            ],
14919            |_| Hsla::green(),
14920            cx,
14921        );
14922
14923        let snapshot = editor.snapshot(window, cx);
14924        let mut highlighted_ranges = editor.background_highlights_in_range(
14925            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14926            &snapshot,
14927            cx.theme(),
14928        );
14929        // Enforce a consistent ordering based on color without relying on the ordering of the
14930        // highlight's `TypeId` which is non-executor.
14931        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14932        assert_eq!(
14933            highlighted_ranges,
14934            &[
14935                (
14936                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14937                    Hsla::red(),
14938                ),
14939                (
14940                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14941                    Hsla::red(),
14942                ),
14943                (
14944                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14945                    Hsla::green(),
14946                ),
14947                (
14948                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14949                    Hsla::green(),
14950                ),
14951            ]
14952        );
14953        assert_eq!(
14954            editor.background_highlights_in_range(
14955                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14956                &snapshot,
14957                cx.theme(),
14958            ),
14959            &[(
14960                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14961                Hsla::red(),
14962            )]
14963        );
14964    });
14965}
14966
14967#[gpui::test]
14968async fn test_following(cx: &mut TestAppContext) {
14969    init_test(cx, |_| {});
14970
14971    let fs = FakeFs::new(cx.executor());
14972    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14973
14974    let buffer = project.update(cx, |project, cx| {
14975        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14976        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14977    });
14978    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14979    let follower = cx.update(|cx| {
14980        cx.open_window(
14981            WindowOptions {
14982                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14983                    gpui::Point::new(px(0.), px(0.)),
14984                    gpui::Point::new(px(10.), px(80.)),
14985                ))),
14986                ..Default::default()
14987            },
14988            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14989        )
14990        .unwrap()
14991    });
14992
14993    let is_still_following = Rc::new(RefCell::new(true));
14994    let follower_edit_event_count = Rc::new(RefCell::new(0));
14995    let pending_update = Rc::new(RefCell::new(None));
14996    let leader_entity = leader.root(cx).unwrap();
14997    let follower_entity = follower.root(cx).unwrap();
14998    _ = follower.update(cx, {
14999        let update = pending_update.clone();
15000        let is_still_following = is_still_following.clone();
15001        let follower_edit_event_count = follower_edit_event_count.clone();
15002        |_, window, cx| {
15003            cx.subscribe_in(
15004                &leader_entity,
15005                window,
15006                move |_, leader, event, window, cx| {
15007                    leader.read(cx).add_event_to_update_proto(
15008                        event,
15009                        &mut update.borrow_mut(),
15010                        window,
15011                        cx,
15012                    );
15013                },
15014            )
15015            .detach();
15016
15017            cx.subscribe_in(
15018                &follower_entity,
15019                window,
15020                move |_, _, event: &EditorEvent, _window, _cx| {
15021                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15022                        *is_still_following.borrow_mut() = false;
15023                    }
15024
15025                    if let EditorEvent::BufferEdited = event {
15026                        *follower_edit_event_count.borrow_mut() += 1;
15027                    }
15028                },
15029            )
15030            .detach();
15031        }
15032    });
15033
15034    // Update the selections only
15035    _ = leader.update(cx, |leader, window, cx| {
15036        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15037            s.select_ranges([1..1])
15038        });
15039    });
15040    follower
15041        .update(cx, |follower, window, cx| {
15042            follower.apply_update_proto(
15043                &project,
15044                pending_update.borrow_mut().take().unwrap(),
15045                window,
15046                cx,
15047            )
15048        })
15049        .unwrap()
15050        .await
15051        .unwrap();
15052    _ = follower.update(cx, |follower, _, cx| {
15053        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15054    });
15055    assert!(*is_still_following.borrow());
15056    assert_eq!(*follower_edit_event_count.borrow(), 0);
15057
15058    // Update the scroll position only
15059    _ = leader.update(cx, |leader, window, cx| {
15060        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15061    });
15062    follower
15063        .update(cx, |follower, window, cx| {
15064            follower.apply_update_proto(
15065                &project,
15066                pending_update.borrow_mut().take().unwrap(),
15067                window,
15068                cx,
15069            )
15070        })
15071        .unwrap()
15072        .await
15073        .unwrap();
15074    assert_eq!(
15075        follower
15076            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15077            .unwrap(),
15078        gpui::Point::new(1.5, 3.5)
15079    );
15080    assert!(*is_still_following.borrow());
15081    assert_eq!(*follower_edit_event_count.borrow(), 0);
15082
15083    // Update the selections and scroll position. The follower's scroll position is updated
15084    // via autoscroll, not via the leader's exact scroll position.
15085    _ = leader.update(cx, |leader, window, cx| {
15086        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15087            s.select_ranges([0..0])
15088        });
15089        leader.request_autoscroll(Autoscroll::newest(), cx);
15090        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15091    });
15092    follower
15093        .update(cx, |follower, window, cx| {
15094            follower.apply_update_proto(
15095                &project,
15096                pending_update.borrow_mut().take().unwrap(),
15097                window,
15098                cx,
15099            )
15100        })
15101        .unwrap()
15102        .await
15103        .unwrap();
15104    _ = follower.update(cx, |follower, _, cx| {
15105        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15106        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15107    });
15108    assert!(*is_still_following.borrow());
15109
15110    // Creating a pending selection that precedes another selection
15111    _ = leader.update(cx, |leader, window, cx| {
15112        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15113            s.select_ranges([1..1])
15114        });
15115        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15116    });
15117    follower
15118        .update(cx, |follower, window, cx| {
15119            follower.apply_update_proto(
15120                &project,
15121                pending_update.borrow_mut().take().unwrap(),
15122                window,
15123                cx,
15124            )
15125        })
15126        .unwrap()
15127        .await
15128        .unwrap();
15129    _ = follower.update(cx, |follower, _, cx| {
15130        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15131    });
15132    assert!(*is_still_following.borrow());
15133
15134    // Extend the pending selection so that it surrounds another selection
15135    _ = leader.update(cx, |leader, window, cx| {
15136        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15137    });
15138    follower
15139        .update(cx, |follower, window, cx| {
15140            follower.apply_update_proto(
15141                &project,
15142                pending_update.borrow_mut().take().unwrap(),
15143                window,
15144                cx,
15145            )
15146        })
15147        .unwrap()
15148        .await
15149        .unwrap();
15150    _ = follower.update(cx, |follower, _, cx| {
15151        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15152    });
15153
15154    // Scrolling locally breaks the follow
15155    _ = follower.update(cx, |follower, window, cx| {
15156        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15157        follower.set_scroll_anchor(
15158            ScrollAnchor {
15159                anchor: top_anchor,
15160                offset: gpui::Point::new(0.0, 0.5),
15161            },
15162            window,
15163            cx,
15164        );
15165    });
15166    assert!(!(*is_still_following.borrow()));
15167}
15168
15169#[gpui::test]
15170async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15171    init_test(cx, |_| {});
15172
15173    let fs = FakeFs::new(cx.executor());
15174    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15175    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15176    let pane = workspace
15177        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15178        .unwrap();
15179
15180    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15181
15182    let leader = pane.update_in(cx, |_, window, cx| {
15183        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15184        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15185    });
15186
15187    // Start following the editor when it has no excerpts.
15188    let mut state_message =
15189        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15190    let workspace_entity = workspace.root(cx).unwrap();
15191    let follower_1 = cx
15192        .update_window(*workspace.deref(), |_, window, cx| {
15193            Editor::from_state_proto(
15194                workspace_entity,
15195                ViewId {
15196                    creator: CollaboratorId::PeerId(PeerId::default()),
15197                    id: 0,
15198                },
15199                &mut state_message,
15200                window,
15201                cx,
15202            )
15203        })
15204        .unwrap()
15205        .unwrap()
15206        .await
15207        .unwrap();
15208
15209    let update_message = Rc::new(RefCell::new(None));
15210    follower_1.update_in(cx, {
15211        let update = update_message.clone();
15212        |_, window, cx| {
15213            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15214                leader.read(cx).add_event_to_update_proto(
15215                    event,
15216                    &mut update.borrow_mut(),
15217                    window,
15218                    cx,
15219                );
15220            })
15221            .detach();
15222        }
15223    });
15224
15225    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15226        (
15227            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15228            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15229        )
15230    });
15231
15232    // Insert some excerpts.
15233    leader.update(cx, |leader, cx| {
15234        leader.buffer.update(cx, |multibuffer, cx| {
15235            multibuffer.set_excerpts_for_path(
15236                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15237                buffer_1.clone(),
15238                vec![
15239                    Point::row_range(0..3),
15240                    Point::row_range(1..6),
15241                    Point::row_range(12..15),
15242                ],
15243                0,
15244                cx,
15245            );
15246            multibuffer.set_excerpts_for_path(
15247                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15248                buffer_2.clone(),
15249                vec![Point::row_range(0..6), Point::row_range(8..12)],
15250                0,
15251                cx,
15252            );
15253        });
15254    });
15255
15256    // Apply the update of adding the excerpts.
15257    follower_1
15258        .update_in(cx, |follower, window, cx| {
15259            follower.apply_update_proto(
15260                &project,
15261                update_message.borrow().clone().unwrap(),
15262                window,
15263                cx,
15264            )
15265        })
15266        .await
15267        .unwrap();
15268    assert_eq!(
15269        follower_1.update(cx, |editor, cx| editor.text(cx)),
15270        leader.update(cx, |editor, cx| editor.text(cx))
15271    );
15272    update_message.borrow_mut().take();
15273
15274    // Start following separately after it already has excerpts.
15275    let mut state_message =
15276        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15277    let workspace_entity = workspace.root(cx).unwrap();
15278    let follower_2 = cx
15279        .update_window(*workspace.deref(), |_, window, cx| {
15280            Editor::from_state_proto(
15281                workspace_entity,
15282                ViewId {
15283                    creator: CollaboratorId::PeerId(PeerId::default()),
15284                    id: 0,
15285                },
15286                &mut state_message,
15287                window,
15288                cx,
15289            )
15290        })
15291        .unwrap()
15292        .unwrap()
15293        .await
15294        .unwrap();
15295    assert_eq!(
15296        follower_2.update(cx, |editor, cx| editor.text(cx)),
15297        leader.update(cx, |editor, cx| editor.text(cx))
15298    );
15299
15300    // Remove some excerpts.
15301    leader.update(cx, |leader, cx| {
15302        leader.buffer.update(cx, |multibuffer, cx| {
15303            let excerpt_ids = multibuffer.excerpt_ids();
15304            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15305            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15306        });
15307    });
15308
15309    // Apply the update of removing the excerpts.
15310    follower_1
15311        .update_in(cx, |follower, window, cx| {
15312            follower.apply_update_proto(
15313                &project,
15314                update_message.borrow().clone().unwrap(),
15315                window,
15316                cx,
15317            )
15318        })
15319        .await
15320        .unwrap();
15321    follower_2
15322        .update_in(cx, |follower, window, cx| {
15323            follower.apply_update_proto(
15324                &project,
15325                update_message.borrow().clone().unwrap(),
15326                window,
15327                cx,
15328            )
15329        })
15330        .await
15331        .unwrap();
15332    update_message.borrow_mut().take();
15333    assert_eq!(
15334        follower_1.update(cx, |editor, cx| editor.text(cx)),
15335        leader.update(cx, |editor, cx| editor.text(cx))
15336    );
15337}
15338
15339#[gpui::test]
15340async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15341    init_test(cx, |_| {});
15342
15343    let mut cx = EditorTestContext::new(cx).await;
15344    let lsp_store =
15345        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15346
15347    cx.set_state(indoc! {"
15348        ˇfn func(abc def: i32) -> u32 {
15349        }
15350    "});
15351
15352    cx.update(|_, cx| {
15353        lsp_store.update(cx, |lsp_store, cx| {
15354            lsp_store
15355                .update_diagnostics(
15356                    LanguageServerId(0),
15357                    lsp::PublishDiagnosticsParams {
15358                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15359                        version: None,
15360                        diagnostics: vec![
15361                            lsp::Diagnostic {
15362                                range: lsp::Range::new(
15363                                    lsp::Position::new(0, 11),
15364                                    lsp::Position::new(0, 12),
15365                                ),
15366                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15367                                ..Default::default()
15368                            },
15369                            lsp::Diagnostic {
15370                                range: lsp::Range::new(
15371                                    lsp::Position::new(0, 12),
15372                                    lsp::Position::new(0, 15),
15373                                ),
15374                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15375                                ..Default::default()
15376                            },
15377                            lsp::Diagnostic {
15378                                range: lsp::Range::new(
15379                                    lsp::Position::new(0, 25),
15380                                    lsp::Position::new(0, 28),
15381                                ),
15382                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15383                                ..Default::default()
15384                            },
15385                        ],
15386                    },
15387                    None,
15388                    DiagnosticSourceKind::Pushed,
15389                    &[],
15390                    cx,
15391                )
15392                .unwrap()
15393        });
15394    });
15395
15396    executor.run_until_parked();
15397
15398    cx.update_editor(|editor, window, cx| {
15399        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15400    });
15401
15402    cx.assert_editor_state(indoc! {"
15403        fn func(abc def: i32) -> ˇu32 {
15404        }
15405    "});
15406
15407    cx.update_editor(|editor, window, cx| {
15408        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15409    });
15410
15411    cx.assert_editor_state(indoc! {"
15412        fn func(abc ˇdef: i32) -> u32 {
15413        }
15414    "});
15415
15416    cx.update_editor(|editor, window, cx| {
15417        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15418    });
15419
15420    cx.assert_editor_state(indoc! {"
15421        fn func(abcˇ def: i32) -> u32 {
15422        }
15423    "});
15424
15425    cx.update_editor(|editor, window, cx| {
15426        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15427    });
15428
15429    cx.assert_editor_state(indoc! {"
15430        fn func(abc def: i32) -> ˇu32 {
15431        }
15432    "});
15433}
15434
15435#[gpui::test]
15436async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15437    init_test(cx, |_| {});
15438
15439    let mut cx = EditorTestContext::new(cx).await;
15440
15441    let diff_base = r#"
15442        use some::mod;
15443
15444        const A: u32 = 42;
15445
15446        fn main() {
15447            println!("hello");
15448
15449            println!("world");
15450        }
15451        "#
15452    .unindent();
15453
15454    // Edits are modified, removed, modified, added
15455    cx.set_state(
15456        &r#"
15457        use some::modified;
15458
15459        ˇ
15460        fn main() {
15461            println!("hello there");
15462
15463            println!("around the");
15464            println!("world");
15465        }
15466        "#
15467        .unindent(),
15468    );
15469
15470    cx.set_head_text(&diff_base);
15471    executor.run_until_parked();
15472
15473    cx.update_editor(|editor, window, cx| {
15474        //Wrap around the bottom of the buffer
15475        for _ in 0..3 {
15476            editor.go_to_next_hunk(&GoToHunk, window, cx);
15477        }
15478    });
15479
15480    cx.assert_editor_state(
15481        &r#"
15482        ˇuse some::modified;
15483
15484
15485        fn main() {
15486            println!("hello there");
15487
15488            println!("around the");
15489            println!("world");
15490        }
15491        "#
15492        .unindent(),
15493    );
15494
15495    cx.update_editor(|editor, window, cx| {
15496        //Wrap around the top of the buffer
15497        for _ in 0..2 {
15498            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15499        }
15500    });
15501
15502    cx.assert_editor_state(
15503        &r#"
15504        use some::modified;
15505
15506
15507        fn main() {
15508        ˇ    println!("hello there");
15509
15510            println!("around the");
15511            println!("world");
15512        }
15513        "#
15514        .unindent(),
15515    );
15516
15517    cx.update_editor(|editor, window, cx| {
15518        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15519    });
15520
15521    cx.assert_editor_state(
15522        &r#"
15523        use some::modified;
15524
15525        ˇ
15526        fn main() {
15527            println!("hello there");
15528
15529            println!("around the");
15530            println!("world");
15531        }
15532        "#
15533        .unindent(),
15534    );
15535
15536    cx.update_editor(|editor, window, cx| {
15537        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15538    });
15539
15540    cx.assert_editor_state(
15541        &r#"
15542        ˇuse some::modified;
15543
15544
15545        fn main() {
15546            println!("hello there");
15547
15548            println!("around the");
15549            println!("world");
15550        }
15551        "#
15552        .unindent(),
15553    );
15554
15555    cx.update_editor(|editor, window, cx| {
15556        for _ in 0..2 {
15557            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15558        }
15559    });
15560
15561    cx.assert_editor_state(
15562        &r#"
15563        use some::modified;
15564
15565
15566        fn main() {
15567        ˇ    println!("hello there");
15568
15569            println!("around the");
15570            println!("world");
15571        }
15572        "#
15573        .unindent(),
15574    );
15575
15576    cx.update_editor(|editor, window, cx| {
15577        editor.fold(&Fold, window, cx);
15578    });
15579
15580    cx.update_editor(|editor, window, cx| {
15581        editor.go_to_next_hunk(&GoToHunk, window, cx);
15582    });
15583
15584    cx.assert_editor_state(
15585        &r#"
15586        ˇuse some::modified;
15587
15588
15589        fn main() {
15590            println!("hello there");
15591
15592            println!("around the");
15593            println!("world");
15594        }
15595        "#
15596        .unindent(),
15597    );
15598}
15599
15600#[test]
15601fn test_split_words() {
15602    fn split(text: &str) -> Vec<&str> {
15603        split_words(text).collect()
15604    }
15605
15606    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15607    assert_eq!(split("hello_world"), &["hello_", "world"]);
15608    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15609    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15610    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15611    assert_eq!(split("helloworld"), &["helloworld"]);
15612
15613    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15614}
15615
15616#[gpui::test]
15617async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15618    init_test(cx, |_| {});
15619
15620    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15621    let mut assert = |before, after| {
15622        let _state_context = cx.set_state(before);
15623        cx.run_until_parked();
15624        cx.update_editor(|editor, window, cx| {
15625            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15626        });
15627        cx.run_until_parked();
15628        cx.assert_editor_state(after);
15629    };
15630
15631    // Outside bracket jumps to outside of matching bracket
15632    assert("console.logˇ(var);", "console.log(var)ˇ;");
15633    assert("console.log(var)ˇ;", "console.logˇ(var);");
15634
15635    // Inside bracket jumps to inside of matching bracket
15636    assert("console.log(ˇvar);", "console.log(varˇ);");
15637    assert("console.log(varˇ);", "console.log(ˇvar);");
15638
15639    // When outside a bracket and inside, favor jumping to the inside bracket
15640    assert(
15641        "console.log('foo', [1, 2, 3]ˇ);",
15642        "console.log(ˇ'foo', [1, 2, 3]);",
15643    );
15644    assert(
15645        "console.log(ˇ'foo', [1, 2, 3]);",
15646        "console.log('foo', [1, 2, 3]ˇ);",
15647    );
15648
15649    // Bias forward if two options are equally likely
15650    assert(
15651        "let result = curried_fun()ˇ();",
15652        "let result = curried_fun()()ˇ;",
15653    );
15654
15655    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15656    assert(
15657        indoc! {"
15658            function test() {
15659                console.log('test')ˇ
15660            }"},
15661        indoc! {"
15662            function test() {
15663                console.logˇ('test')
15664            }"},
15665    );
15666}
15667
15668#[gpui::test]
15669async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15670    init_test(cx, |_| {});
15671
15672    let fs = FakeFs::new(cx.executor());
15673    fs.insert_tree(
15674        path!("/a"),
15675        json!({
15676            "main.rs": "fn main() { let a = 5; }",
15677            "other.rs": "// Test file",
15678        }),
15679    )
15680    .await;
15681    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15682
15683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15684    language_registry.add(Arc::new(Language::new(
15685        LanguageConfig {
15686            name: "Rust".into(),
15687            matcher: LanguageMatcher {
15688                path_suffixes: vec!["rs".to_string()],
15689                ..Default::default()
15690            },
15691            brackets: BracketPairConfig {
15692                pairs: vec![BracketPair {
15693                    start: "{".to_string(),
15694                    end: "}".to_string(),
15695                    close: true,
15696                    surround: true,
15697                    newline: true,
15698                }],
15699                disabled_scopes_by_bracket_ix: Vec::new(),
15700            },
15701            ..Default::default()
15702        },
15703        Some(tree_sitter_rust::LANGUAGE.into()),
15704    )));
15705    let mut fake_servers = language_registry.register_fake_lsp(
15706        "Rust",
15707        FakeLspAdapter {
15708            capabilities: lsp::ServerCapabilities {
15709                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15710                    first_trigger_character: "{".to_string(),
15711                    more_trigger_character: None,
15712                }),
15713                ..Default::default()
15714            },
15715            ..Default::default()
15716        },
15717    );
15718
15719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15720
15721    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15722
15723    let worktree_id = workspace
15724        .update(cx, |workspace, _, cx| {
15725            workspace.project().update(cx, |project, cx| {
15726                project.worktrees(cx).next().unwrap().read(cx).id()
15727            })
15728        })
15729        .unwrap();
15730
15731    let buffer = project
15732        .update(cx, |project, cx| {
15733            project.open_local_buffer(path!("/a/main.rs"), cx)
15734        })
15735        .await
15736        .unwrap();
15737    let editor_handle = workspace
15738        .update(cx, |workspace, window, cx| {
15739            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15740        })
15741        .unwrap()
15742        .await
15743        .unwrap()
15744        .downcast::<Editor>()
15745        .unwrap();
15746
15747    cx.executor().start_waiting();
15748    let fake_server = fake_servers.next().await.unwrap();
15749
15750    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15751        |params, _| async move {
15752            assert_eq!(
15753                params.text_document_position.text_document.uri,
15754                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15755            );
15756            assert_eq!(
15757                params.text_document_position.position,
15758                lsp::Position::new(0, 21),
15759            );
15760
15761            Ok(Some(vec![lsp::TextEdit {
15762                new_text: "]".to_string(),
15763                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15764            }]))
15765        },
15766    );
15767
15768    editor_handle.update_in(cx, |editor, window, cx| {
15769        window.focus(&editor.focus_handle(cx));
15770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15771            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15772        });
15773        editor.handle_input("{", window, cx);
15774    });
15775
15776    cx.executor().run_until_parked();
15777
15778    buffer.update(cx, |buffer, _| {
15779        assert_eq!(
15780            buffer.text(),
15781            "fn main() { let a = {5}; }",
15782            "No extra braces from on type formatting should appear in the buffer"
15783        )
15784    });
15785}
15786
15787#[gpui::test(iterations = 20, seeds(31))]
15788async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15789    init_test(cx, |_| {});
15790
15791    let mut cx = EditorLspTestContext::new_rust(
15792        lsp::ServerCapabilities {
15793            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15794                first_trigger_character: ".".to_string(),
15795                more_trigger_character: None,
15796            }),
15797            ..Default::default()
15798        },
15799        cx,
15800    )
15801    .await;
15802
15803    cx.update_buffer(|buffer, _| {
15804        // This causes autoindent to be async.
15805        buffer.set_sync_parse_timeout(Duration::ZERO)
15806    });
15807
15808    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15809    cx.simulate_keystroke("\n");
15810    cx.run_until_parked();
15811
15812    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
15813    let mut request =
15814        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15815            let buffer_cloned = buffer_cloned.clone();
15816            async move {
15817                buffer_cloned.update(&mut cx, |buffer, _| {
15818                    assert_eq!(
15819                        buffer.text(),
15820                        "fn c() {\n    d()\n        .\n}\n",
15821                        "OnTypeFormatting should triggered after autoindent applied"
15822                    )
15823                })?;
15824
15825                Ok(Some(vec![]))
15826            }
15827        });
15828
15829    cx.simulate_keystroke(".");
15830    cx.run_until_parked();
15831
15832    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15833    assert!(request.next().await.is_some());
15834    request.close();
15835    assert!(request.next().await.is_none());
15836}
15837
15838#[gpui::test]
15839async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15840    init_test(cx, |_| {});
15841
15842    let fs = FakeFs::new(cx.executor());
15843    fs.insert_tree(
15844        path!("/a"),
15845        json!({
15846            "main.rs": "fn main() { let a = 5; }",
15847            "other.rs": "// Test file",
15848        }),
15849    )
15850    .await;
15851
15852    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15853
15854    let server_restarts = Arc::new(AtomicUsize::new(0));
15855    let closure_restarts = Arc::clone(&server_restarts);
15856    let language_server_name = "test language server";
15857    let language_name: LanguageName = "Rust".into();
15858
15859    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15860    language_registry.add(Arc::new(Language::new(
15861        LanguageConfig {
15862            name: language_name.clone(),
15863            matcher: LanguageMatcher {
15864                path_suffixes: vec!["rs".to_string()],
15865                ..Default::default()
15866            },
15867            ..Default::default()
15868        },
15869        Some(tree_sitter_rust::LANGUAGE.into()),
15870    )));
15871    let mut fake_servers = language_registry.register_fake_lsp(
15872        "Rust",
15873        FakeLspAdapter {
15874            name: language_server_name,
15875            initialization_options: Some(json!({
15876                "testOptionValue": true
15877            })),
15878            initializer: Some(Box::new(move |fake_server| {
15879                let task_restarts = Arc::clone(&closure_restarts);
15880                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15881                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15882                    futures::future::ready(Ok(()))
15883                });
15884            })),
15885            ..Default::default()
15886        },
15887    );
15888
15889    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15890    let _buffer = project
15891        .update(cx, |project, cx| {
15892            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15893        })
15894        .await
15895        .unwrap();
15896    let _fake_server = fake_servers.next().await.unwrap();
15897    update_test_language_settings(cx, |language_settings| {
15898        language_settings.languages.0.insert(
15899            language_name.clone(),
15900            LanguageSettingsContent {
15901                tab_size: NonZeroU32::new(8),
15902                ..Default::default()
15903            },
15904        );
15905    });
15906    cx.executor().run_until_parked();
15907    assert_eq!(
15908        server_restarts.load(atomic::Ordering::Acquire),
15909        0,
15910        "Should not restart LSP server on an unrelated change"
15911    );
15912
15913    update_test_project_settings(cx, |project_settings| {
15914        project_settings.lsp.insert(
15915            "Some other server name".into(),
15916            LspSettings {
15917                binary: None,
15918                settings: None,
15919                initialization_options: Some(json!({
15920                    "some other init value": false
15921                })),
15922                enable_lsp_tasks: false,
15923            },
15924        );
15925    });
15926    cx.executor().run_until_parked();
15927    assert_eq!(
15928        server_restarts.load(atomic::Ordering::Acquire),
15929        0,
15930        "Should not restart LSP server on an unrelated LSP settings change"
15931    );
15932
15933    update_test_project_settings(cx, |project_settings| {
15934        project_settings.lsp.insert(
15935            language_server_name.into(),
15936            LspSettings {
15937                binary: None,
15938                settings: None,
15939                initialization_options: Some(json!({
15940                    "anotherInitValue": false
15941                })),
15942                enable_lsp_tasks: false,
15943            },
15944        );
15945    });
15946    cx.executor().run_until_parked();
15947    assert_eq!(
15948        server_restarts.load(atomic::Ordering::Acquire),
15949        1,
15950        "Should restart LSP server on a related LSP settings change"
15951    );
15952
15953    update_test_project_settings(cx, |project_settings| {
15954        project_settings.lsp.insert(
15955            language_server_name.into(),
15956            LspSettings {
15957                binary: None,
15958                settings: None,
15959                initialization_options: Some(json!({
15960                    "anotherInitValue": false
15961                })),
15962                enable_lsp_tasks: false,
15963            },
15964        );
15965    });
15966    cx.executor().run_until_parked();
15967    assert_eq!(
15968        server_restarts.load(atomic::Ordering::Acquire),
15969        1,
15970        "Should not restart LSP server on a related LSP settings change that is the same"
15971    );
15972
15973    update_test_project_settings(cx, |project_settings| {
15974        project_settings.lsp.insert(
15975            language_server_name.into(),
15976            LspSettings {
15977                binary: None,
15978                settings: None,
15979                initialization_options: None,
15980                enable_lsp_tasks: false,
15981            },
15982        );
15983    });
15984    cx.executor().run_until_parked();
15985    assert_eq!(
15986        server_restarts.load(atomic::Ordering::Acquire),
15987        2,
15988        "Should restart LSP server on another related LSP settings change"
15989    );
15990}
15991
15992#[gpui::test]
15993async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15994    init_test(cx, |_| {});
15995
15996    let mut cx = EditorLspTestContext::new_rust(
15997        lsp::ServerCapabilities {
15998            completion_provider: Some(lsp::CompletionOptions {
15999                trigger_characters: Some(vec![".".to_string()]),
16000                resolve_provider: Some(true),
16001                ..Default::default()
16002            }),
16003            ..Default::default()
16004        },
16005        cx,
16006    )
16007    .await;
16008
16009    cx.set_state("fn main() { let a = 2ˇ; }");
16010    cx.simulate_keystroke(".");
16011    let completion_item = lsp::CompletionItem {
16012        label: "some".into(),
16013        kind: Some(lsp::CompletionItemKind::SNIPPET),
16014        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16015        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16016            kind: lsp::MarkupKind::Markdown,
16017            value: "```rust\nSome(2)\n```".to_string(),
16018        })),
16019        deprecated: Some(false),
16020        sort_text: Some("fffffff2".to_string()),
16021        filter_text: Some("some".to_string()),
16022        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16023        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16024            range: lsp::Range {
16025                start: lsp::Position {
16026                    line: 0,
16027                    character: 22,
16028                },
16029                end: lsp::Position {
16030                    line: 0,
16031                    character: 22,
16032                },
16033            },
16034            new_text: "Some(2)".to_string(),
16035        })),
16036        additional_text_edits: Some(vec![lsp::TextEdit {
16037            range: lsp::Range {
16038                start: lsp::Position {
16039                    line: 0,
16040                    character: 20,
16041                },
16042                end: lsp::Position {
16043                    line: 0,
16044                    character: 22,
16045                },
16046            },
16047            new_text: "".to_string(),
16048        }]),
16049        ..Default::default()
16050    };
16051
16052    let closure_completion_item = completion_item.clone();
16053    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16054        let task_completion_item = closure_completion_item.clone();
16055        async move {
16056            Ok(Some(lsp::CompletionResponse::Array(vec![
16057                task_completion_item,
16058            ])))
16059        }
16060    });
16061
16062    request.next().await;
16063
16064    cx.condition(|editor, _| editor.context_menu_visible())
16065        .await;
16066    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16067        editor
16068            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16069            .unwrap()
16070    });
16071    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16072
16073    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16074        let task_completion_item = completion_item.clone();
16075        async move { Ok(task_completion_item) }
16076    })
16077    .next()
16078    .await
16079    .unwrap();
16080    apply_additional_edits.await.unwrap();
16081    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16082}
16083
16084#[gpui::test]
16085async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16086    init_test(cx, |_| {});
16087
16088    let mut cx = EditorLspTestContext::new_rust(
16089        lsp::ServerCapabilities {
16090            completion_provider: Some(lsp::CompletionOptions {
16091                trigger_characters: Some(vec![".".to_string()]),
16092                resolve_provider: Some(true),
16093                ..Default::default()
16094            }),
16095            ..Default::default()
16096        },
16097        cx,
16098    )
16099    .await;
16100
16101    cx.set_state("fn main() { let a = 2ˇ; }");
16102    cx.simulate_keystroke(".");
16103
16104    let item1 = lsp::CompletionItem {
16105        label: "method id()".to_string(),
16106        filter_text: Some("id".to_string()),
16107        detail: None,
16108        documentation: None,
16109        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16110            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16111            new_text: ".id".to_string(),
16112        })),
16113        ..lsp::CompletionItem::default()
16114    };
16115
16116    let item2 = lsp::CompletionItem {
16117        label: "other".to_string(),
16118        filter_text: Some("other".to_string()),
16119        detail: None,
16120        documentation: None,
16121        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16122            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16123            new_text: ".other".to_string(),
16124        })),
16125        ..lsp::CompletionItem::default()
16126    };
16127
16128    let item1 = item1.clone();
16129    cx.set_request_handler::<lsp::request::Completion, _, _>({
16130        let item1 = item1.clone();
16131        move |_, _, _| {
16132            let item1 = item1.clone();
16133            let item2 = item2.clone();
16134            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16135        }
16136    })
16137    .next()
16138    .await;
16139
16140    cx.condition(|editor, _| editor.context_menu_visible())
16141        .await;
16142    cx.update_editor(|editor, _, _| {
16143        let context_menu = editor.context_menu.borrow_mut();
16144        let context_menu = context_menu
16145            .as_ref()
16146            .expect("Should have the context menu deployed");
16147        match context_menu {
16148            CodeContextMenu::Completions(completions_menu) => {
16149                let completions = completions_menu.completions.borrow_mut();
16150                assert_eq!(
16151                    completions
16152                        .iter()
16153                        .map(|completion| &completion.label.text)
16154                        .collect::<Vec<_>>(),
16155                    vec!["method id()", "other"]
16156                )
16157            }
16158            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16159        }
16160    });
16161
16162    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16163        let item1 = item1.clone();
16164        move |_, item_to_resolve, _| {
16165            let item1 = item1.clone();
16166            async move {
16167                if item1 == item_to_resolve {
16168                    Ok(lsp::CompletionItem {
16169                        label: "method id()".to_string(),
16170                        filter_text: Some("id".to_string()),
16171                        detail: Some("Now resolved!".to_string()),
16172                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16173                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16174                            range: lsp::Range::new(
16175                                lsp::Position::new(0, 22),
16176                                lsp::Position::new(0, 22),
16177                            ),
16178                            new_text: ".id".to_string(),
16179                        })),
16180                        ..lsp::CompletionItem::default()
16181                    })
16182                } else {
16183                    Ok(item_to_resolve)
16184                }
16185            }
16186        }
16187    })
16188    .next()
16189    .await
16190    .unwrap();
16191    cx.run_until_parked();
16192
16193    cx.update_editor(|editor, window, cx| {
16194        editor.context_menu_next(&Default::default(), window, cx);
16195    });
16196
16197    cx.update_editor(|editor, _, _| {
16198        let context_menu = editor.context_menu.borrow_mut();
16199        let context_menu = context_menu
16200            .as_ref()
16201            .expect("Should have the context menu deployed");
16202        match context_menu {
16203            CodeContextMenu::Completions(completions_menu) => {
16204                let completions = completions_menu.completions.borrow_mut();
16205                assert_eq!(
16206                    completions
16207                        .iter()
16208                        .map(|completion| &completion.label.text)
16209                        .collect::<Vec<_>>(),
16210                    vec!["method id() Now resolved!", "other"],
16211                    "Should update first completion label, but not second as the filter text did not match."
16212                );
16213            }
16214            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16215        }
16216    });
16217}
16218
16219#[gpui::test]
16220async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16221    init_test(cx, |_| {});
16222    let mut cx = EditorLspTestContext::new_rust(
16223        lsp::ServerCapabilities {
16224            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16225            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16226            completion_provider: Some(lsp::CompletionOptions {
16227                resolve_provider: Some(true),
16228                ..Default::default()
16229            }),
16230            ..Default::default()
16231        },
16232        cx,
16233    )
16234    .await;
16235    cx.set_state(indoc! {"
16236        struct TestStruct {
16237            field: i32
16238        }
16239
16240        fn mainˇ() {
16241            let unused_var = 42;
16242            let test_struct = TestStruct { field: 42 };
16243        }
16244    "});
16245    let symbol_range = cx.lsp_range(indoc! {"
16246        struct TestStruct {
16247            field: i32
16248        }
16249
16250        «fn main»() {
16251            let unused_var = 42;
16252            let test_struct = TestStruct { field: 42 };
16253        }
16254    "});
16255    let mut hover_requests =
16256        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16257            Ok(Some(lsp::Hover {
16258                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16259                    kind: lsp::MarkupKind::Markdown,
16260                    value: "Function documentation".to_string(),
16261                }),
16262                range: Some(symbol_range),
16263            }))
16264        });
16265
16266    // Case 1: Test that code action menu hide hover popover
16267    cx.dispatch_action(Hover);
16268    hover_requests.next().await;
16269    cx.condition(|editor, _| editor.hover_state.visible()).await;
16270    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16271        move |_, _, _| async move {
16272            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16273                lsp::CodeAction {
16274                    title: "Remove unused variable".to_string(),
16275                    kind: Some(CodeActionKind::QUICKFIX),
16276                    edit: Some(lsp::WorkspaceEdit {
16277                        changes: Some(
16278                            [(
16279                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16280                                vec![lsp::TextEdit {
16281                                    range: lsp::Range::new(
16282                                        lsp::Position::new(5, 4),
16283                                        lsp::Position::new(5, 27),
16284                                    ),
16285                                    new_text: "".to_string(),
16286                                }],
16287                            )]
16288                            .into_iter()
16289                            .collect(),
16290                        ),
16291                        ..Default::default()
16292                    }),
16293                    ..Default::default()
16294                },
16295            )]))
16296        },
16297    );
16298    cx.update_editor(|editor, window, cx| {
16299        editor.toggle_code_actions(
16300            &ToggleCodeActions {
16301                deployed_from: None,
16302                quick_launch: false,
16303            },
16304            window,
16305            cx,
16306        );
16307    });
16308    code_action_requests.next().await;
16309    cx.run_until_parked();
16310    cx.condition(|editor, _| editor.context_menu_visible())
16311        .await;
16312    cx.update_editor(|editor, _, _| {
16313        assert!(
16314            !editor.hover_state.visible(),
16315            "Hover popover should be hidden when code action menu is shown"
16316        );
16317        // Hide code actions
16318        editor.context_menu.take();
16319    });
16320
16321    // Case 2: Test that code completions hide hover popover
16322    cx.dispatch_action(Hover);
16323    hover_requests.next().await;
16324    cx.condition(|editor, _| editor.hover_state.visible()).await;
16325    let counter = Arc::new(AtomicUsize::new(0));
16326    let mut completion_requests =
16327        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16328            let counter = counter.clone();
16329            async move {
16330                counter.fetch_add(1, atomic::Ordering::Release);
16331                Ok(Some(lsp::CompletionResponse::Array(vec![
16332                    lsp::CompletionItem {
16333                        label: "main".into(),
16334                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16335                        detail: Some("() -> ()".to_string()),
16336                        ..Default::default()
16337                    },
16338                    lsp::CompletionItem {
16339                        label: "TestStruct".into(),
16340                        kind: Some(lsp::CompletionItemKind::STRUCT),
16341                        detail: Some("struct TestStruct".to_string()),
16342                        ..Default::default()
16343                    },
16344                ])))
16345            }
16346        });
16347    cx.update_editor(|editor, window, cx| {
16348        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16349    });
16350    completion_requests.next().await;
16351    cx.condition(|editor, _| editor.context_menu_visible())
16352        .await;
16353    cx.update_editor(|editor, _, _| {
16354        assert!(
16355            !editor.hover_state.visible(),
16356            "Hover popover should be hidden when completion menu is shown"
16357        );
16358    });
16359}
16360
16361#[gpui::test]
16362async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16363    init_test(cx, |_| {});
16364
16365    let mut cx = EditorLspTestContext::new_rust(
16366        lsp::ServerCapabilities {
16367            completion_provider: Some(lsp::CompletionOptions {
16368                trigger_characters: Some(vec![".".to_string()]),
16369                resolve_provider: Some(true),
16370                ..Default::default()
16371            }),
16372            ..Default::default()
16373        },
16374        cx,
16375    )
16376    .await;
16377
16378    cx.set_state("fn main() { let a = 2ˇ; }");
16379    cx.simulate_keystroke(".");
16380
16381    let unresolved_item_1 = lsp::CompletionItem {
16382        label: "id".to_string(),
16383        filter_text: Some("id".to_string()),
16384        detail: None,
16385        documentation: None,
16386        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16387            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16388            new_text: ".id".to_string(),
16389        })),
16390        ..lsp::CompletionItem::default()
16391    };
16392    let resolved_item_1 = lsp::CompletionItem {
16393        additional_text_edits: Some(vec![lsp::TextEdit {
16394            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16395            new_text: "!!".to_string(),
16396        }]),
16397        ..unresolved_item_1.clone()
16398    };
16399    let unresolved_item_2 = lsp::CompletionItem {
16400        label: "other".to_string(),
16401        filter_text: Some("other".to_string()),
16402        detail: None,
16403        documentation: None,
16404        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16405            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16406            new_text: ".other".to_string(),
16407        })),
16408        ..lsp::CompletionItem::default()
16409    };
16410    let resolved_item_2 = lsp::CompletionItem {
16411        additional_text_edits: Some(vec![lsp::TextEdit {
16412            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16413            new_text: "??".to_string(),
16414        }]),
16415        ..unresolved_item_2.clone()
16416    };
16417
16418    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16419    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16420    cx.lsp
16421        .server
16422        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16423            let unresolved_item_1 = unresolved_item_1.clone();
16424            let resolved_item_1 = resolved_item_1.clone();
16425            let unresolved_item_2 = unresolved_item_2.clone();
16426            let resolved_item_2 = resolved_item_2.clone();
16427            let resolve_requests_1 = resolve_requests_1.clone();
16428            let resolve_requests_2 = resolve_requests_2.clone();
16429            move |unresolved_request, _| {
16430                let unresolved_item_1 = unresolved_item_1.clone();
16431                let resolved_item_1 = resolved_item_1.clone();
16432                let unresolved_item_2 = unresolved_item_2.clone();
16433                let resolved_item_2 = resolved_item_2.clone();
16434                let resolve_requests_1 = resolve_requests_1.clone();
16435                let resolve_requests_2 = resolve_requests_2.clone();
16436                async move {
16437                    if unresolved_request == unresolved_item_1 {
16438                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16439                        Ok(resolved_item_1.clone())
16440                    } else if unresolved_request == unresolved_item_2 {
16441                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16442                        Ok(resolved_item_2.clone())
16443                    } else {
16444                        panic!("Unexpected completion item {unresolved_request:?}")
16445                    }
16446                }
16447            }
16448        })
16449        .detach();
16450
16451    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16452        let unresolved_item_1 = unresolved_item_1.clone();
16453        let unresolved_item_2 = unresolved_item_2.clone();
16454        async move {
16455            Ok(Some(lsp::CompletionResponse::Array(vec![
16456                unresolved_item_1,
16457                unresolved_item_2,
16458            ])))
16459        }
16460    })
16461    .next()
16462    .await;
16463
16464    cx.condition(|editor, _| editor.context_menu_visible())
16465        .await;
16466    cx.update_editor(|editor, _, _| {
16467        let context_menu = editor.context_menu.borrow_mut();
16468        let context_menu = context_menu
16469            .as_ref()
16470            .expect("Should have the context menu deployed");
16471        match context_menu {
16472            CodeContextMenu::Completions(completions_menu) => {
16473                let completions = completions_menu.completions.borrow_mut();
16474                assert_eq!(
16475                    completions
16476                        .iter()
16477                        .map(|completion| &completion.label.text)
16478                        .collect::<Vec<_>>(),
16479                    vec!["id", "other"]
16480                )
16481            }
16482            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16483        }
16484    });
16485    cx.run_until_parked();
16486
16487    cx.update_editor(|editor, window, cx| {
16488        editor.context_menu_next(&ContextMenuNext, window, cx);
16489    });
16490    cx.run_until_parked();
16491    cx.update_editor(|editor, window, cx| {
16492        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16493    });
16494    cx.run_until_parked();
16495    cx.update_editor(|editor, window, cx| {
16496        editor.context_menu_next(&ContextMenuNext, window, cx);
16497    });
16498    cx.run_until_parked();
16499    cx.update_editor(|editor, window, cx| {
16500        editor
16501            .compose_completion(&ComposeCompletion::default(), window, cx)
16502            .expect("No task returned")
16503    })
16504    .await
16505    .expect("Completion failed");
16506    cx.run_until_parked();
16507
16508    cx.update_editor(|editor, _, cx| {
16509        assert_eq!(
16510            resolve_requests_1.load(atomic::Ordering::Acquire),
16511            1,
16512            "Should always resolve once despite multiple selections"
16513        );
16514        assert_eq!(
16515            resolve_requests_2.load(atomic::Ordering::Acquire),
16516            1,
16517            "Should always resolve once after multiple selections and applying the completion"
16518        );
16519        assert_eq!(
16520            editor.text(cx),
16521            "fn main() { let a = ??.other; }",
16522            "Should use resolved data when applying the completion"
16523        );
16524    });
16525}
16526
16527#[gpui::test]
16528async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16529    init_test(cx, |_| {});
16530
16531    let item_0 = lsp::CompletionItem {
16532        label: "abs".into(),
16533        insert_text: Some("abs".into()),
16534        data: Some(json!({ "very": "special"})),
16535        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16536        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16537            lsp::InsertReplaceEdit {
16538                new_text: "abs".to_string(),
16539                insert: lsp::Range::default(),
16540                replace: lsp::Range::default(),
16541            },
16542        )),
16543        ..lsp::CompletionItem::default()
16544    };
16545    let items = iter::once(item_0.clone())
16546        .chain((11..51).map(|i| lsp::CompletionItem {
16547            label: format!("item_{}", i),
16548            insert_text: Some(format!("item_{}", i)),
16549            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16550            ..lsp::CompletionItem::default()
16551        }))
16552        .collect::<Vec<_>>();
16553
16554    let default_commit_characters = vec!["?".to_string()];
16555    let default_data = json!({ "default": "data"});
16556    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16557    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16558    let default_edit_range = lsp::Range {
16559        start: lsp::Position {
16560            line: 0,
16561            character: 5,
16562        },
16563        end: lsp::Position {
16564            line: 0,
16565            character: 5,
16566        },
16567    };
16568
16569    let mut cx = EditorLspTestContext::new_rust(
16570        lsp::ServerCapabilities {
16571            completion_provider: Some(lsp::CompletionOptions {
16572                trigger_characters: Some(vec![".".to_string()]),
16573                resolve_provider: Some(true),
16574                ..Default::default()
16575            }),
16576            ..Default::default()
16577        },
16578        cx,
16579    )
16580    .await;
16581
16582    cx.set_state("fn main() { let a = 2ˇ; }");
16583    cx.simulate_keystroke(".");
16584
16585    let completion_data = default_data.clone();
16586    let completion_characters = default_commit_characters.clone();
16587    let completion_items = items.clone();
16588    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16589        let default_data = completion_data.clone();
16590        let default_commit_characters = completion_characters.clone();
16591        let items = completion_items.clone();
16592        async move {
16593            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16594                items,
16595                item_defaults: Some(lsp::CompletionListItemDefaults {
16596                    data: Some(default_data.clone()),
16597                    commit_characters: Some(default_commit_characters.clone()),
16598                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16599                        default_edit_range,
16600                    )),
16601                    insert_text_format: Some(default_insert_text_format),
16602                    insert_text_mode: Some(default_insert_text_mode),
16603                }),
16604                ..lsp::CompletionList::default()
16605            })))
16606        }
16607    })
16608    .next()
16609    .await;
16610
16611    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16612    cx.lsp
16613        .server
16614        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16615            let closure_resolved_items = resolved_items.clone();
16616            move |item_to_resolve, _| {
16617                let closure_resolved_items = closure_resolved_items.clone();
16618                async move {
16619                    closure_resolved_items.lock().push(item_to_resolve.clone());
16620                    Ok(item_to_resolve)
16621                }
16622            }
16623        })
16624        .detach();
16625
16626    cx.condition(|editor, _| editor.context_menu_visible())
16627        .await;
16628    cx.run_until_parked();
16629    cx.update_editor(|editor, _, _| {
16630        let menu = editor.context_menu.borrow_mut();
16631        match menu.as_ref().expect("should have the completions menu") {
16632            CodeContextMenu::Completions(completions_menu) => {
16633                assert_eq!(
16634                    completions_menu
16635                        .entries
16636                        .borrow()
16637                        .iter()
16638                        .map(|mat| mat.string.clone())
16639                        .collect::<Vec<String>>(),
16640                    items
16641                        .iter()
16642                        .map(|completion| completion.label.clone())
16643                        .collect::<Vec<String>>()
16644                );
16645            }
16646            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16647        }
16648    });
16649    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16650    // with 4 from the end.
16651    assert_eq!(
16652        *resolved_items.lock(),
16653        [&items[0..16], &items[items.len() - 4..items.len()]]
16654            .concat()
16655            .iter()
16656            .cloned()
16657            .map(|mut item| {
16658                if item.data.is_none() {
16659                    item.data = Some(default_data.clone());
16660                }
16661                item
16662            })
16663            .collect::<Vec<lsp::CompletionItem>>(),
16664        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16665    );
16666    resolved_items.lock().clear();
16667
16668    cx.update_editor(|editor, window, cx| {
16669        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16670    });
16671    cx.run_until_parked();
16672    // Completions that have already been resolved are skipped.
16673    assert_eq!(
16674        *resolved_items.lock(),
16675        items[items.len() - 17..items.len() - 4]
16676            .iter()
16677            .cloned()
16678            .map(|mut item| {
16679                if item.data.is_none() {
16680                    item.data = Some(default_data.clone());
16681                }
16682                item
16683            })
16684            .collect::<Vec<lsp::CompletionItem>>()
16685    );
16686    resolved_items.lock().clear();
16687}
16688
16689#[gpui::test]
16690async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16691    init_test(cx, |_| {});
16692
16693    let mut cx = EditorLspTestContext::new(
16694        Language::new(
16695            LanguageConfig {
16696                matcher: LanguageMatcher {
16697                    path_suffixes: vec!["jsx".into()],
16698                    ..Default::default()
16699                },
16700                overrides: [(
16701                    "element".into(),
16702                    LanguageConfigOverride {
16703                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16704                        ..Default::default()
16705                    },
16706                )]
16707                .into_iter()
16708                .collect(),
16709                ..Default::default()
16710            },
16711            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16712        )
16713        .with_override_query("(jsx_self_closing_element) @element")
16714        .unwrap(),
16715        lsp::ServerCapabilities {
16716            completion_provider: Some(lsp::CompletionOptions {
16717                trigger_characters: Some(vec![":".to_string()]),
16718                ..Default::default()
16719            }),
16720            ..Default::default()
16721        },
16722        cx,
16723    )
16724    .await;
16725
16726    cx.lsp
16727        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16728            Ok(Some(lsp::CompletionResponse::Array(vec![
16729                lsp::CompletionItem {
16730                    label: "bg-blue".into(),
16731                    ..Default::default()
16732                },
16733                lsp::CompletionItem {
16734                    label: "bg-red".into(),
16735                    ..Default::default()
16736                },
16737                lsp::CompletionItem {
16738                    label: "bg-yellow".into(),
16739                    ..Default::default()
16740                },
16741            ])))
16742        });
16743
16744    cx.set_state(r#"<p class="bgˇ" />"#);
16745
16746    // Trigger completion when typing a dash, because the dash is an extra
16747    // word character in the 'element' scope, which contains the cursor.
16748    cx.simulate_keystroke("-");
16749    cx.executor().run_until_parked();
16750    cx.update_editor(|editor, _, _| {
16751        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16752        {
16753            assert_eq!(
16754                completion_menu_entries(menu),
16755                &["bg-blue", "bg-red", "bg-yellow"]
16756            );
16757        } else {
16758            panic!("expected completion menu to be open");
16759        }
16760    });
16761
16762    cx.simulate_keystroke("l");
16763    cx.executor().run_until_parked();
16764    cx.update_editor(|editor, _, _| {
16765        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16766        {
16767            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16768        } else {
16769            panic!("expected completion menu to be open");
16770        }
16771    });
16772
16773    // When filtering completions, consider the character after the '-' to
16774    // be the start of a subword.
16775    cx.set_state(r#"<p class="yelˇ" />"#);
16776    cx.simulate_keystroke("l");
16777    cx.executor().run_until_parked();
16778    cx.update_editor(|editor, _, _| {
16779        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16780        {
16781            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16782        } else {
16783            panic!("expected completion menu to be open");
16784        }
16785    });
16786}
16787
16788fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16789    let entries = menu.entries.borrow();
16790    entries.iter().map(|mat| mat.string.clone()).collect()
16791}
16792
16793#[gpui::test]
16794async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16795    init_test(cx, |settings| {
16796        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16797            Formatter::Prettier,
16798        )))
16799    });
16800
16801    let fs = FakeFs::new(cx.executor());
16802    fs.insert_file(path!("/file.ts"), Default::default()).await;
16803
16804    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16805    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16806
16807    language_registry.add(Arc::new(Language::new(
16808        LanguageConfig {
16809            name: "TypeScript".into(),
16810            matcher: LanguageMatcher {
16811                path_suffixes: vec!["ts".to_string()],
16812                ..Default::default()
16813            },
16814            ..Default::default()
16815        },
16816        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16817    )));
16818    update_test_language_settings(cx, |settings| {
16819        settings.defaults.prettier = Some(PrettierSettings {
16820            allowed: true,
16821            ..PrettierSettings::default()
16822        });
16823    });
16824
16825    let test_plugin = "test_plugin";
16826    let _ = language_registry.register_fake_lsp(
16827        "TypeScript",
16828        FakeLspAdapter {
16829            prettier_plugins: vec![test_plugin],
16830            ..Default::default()
16831        },
16832    );
16833
16834    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16835    let buffer = project
16836        .update(cx, |project, cx| {
16837            project.open_local_buffer(path!("/file.ts"), cx)
16838        })
16839        .await
16840        .unwrap();
16841
16842    let buffer_text = "one\ntwo\nthree\n";
16843    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16844    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16845    editor.update_in(cx, |editor, window, cx| {
16846        editor.set_text(buffer_text, window, cx)
16847    });
16848
16849    editor
16850        .update_in(cx, |editor, window, cx| {
16851            editor.perform_format(
16852                project.clone(),
16853                FormatTrigger::Manual,
16854                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16855                window,
16856                cx,
16857            )
16858        })
16859        .unwrap()
16860        .await;
16861    assert_eq!(
16862        editor.update(cx, |editor, cx| editor.text(cx)),
16863        buffer_text.to_string() + prettier_format_suffix,
16864        "Test prettier formatting was not applied to the original buffer text",
16865    );
16866
16867    update_test_language_settings(cx, |settings| {
16868        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16869    });
16870    let format = editor.update_in(cx, |editor, window, cx| {
16871        editor.perform_format(
16872            project.clone(),
16873            FormatTrigger::Manual,
16874            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16875            window,
16876            cx,
16877        )
16878    });
16879    format.await.unwrap();
16880    assert_eq!(
16881        editor.update(cx, |editor, cx| editor.text(cx)),
16882        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16883        "Autoformatting (via test prettier) was not applied to the original buffer text",
16884    );
16885}
16886
16887#[gpui::test]
16888async fn test_addition_reverts(cx: &mut TestAppContext) {
16889    init_test(cx, |_| {});
16890    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16891    let base_text = indoc! {r#"
16892        struct Row;
16893        struct Row1;
16894        struct Row2;
16895
16896        struct Row4;
16897        struct Row5;
16898        struct Row6;
16899
16900        struct Row8;
16901        struct Row9;
16902        struct Row10;"#};
16903
16904    // When addition hunks are not adjacent to carets, no hunk revert is performed
16905    assert_hunk_revert(
16906        indoc! {r#"struct Row;
16907                   struct Row1;
16908                   struct Row1.1;
16909                   struct Row1.2;
16910                   struct Row2;ˇ
16911
16912                   struct Row4;
16913                   struct Row5;
16914                   struct Row6;
16915
16916                   struct Row8;
16917                   ˇstruct Row9;
16918                   struct Row9.1;
16919                   struct Row9.2;
16920                   struct Row9.3;
16921                   struct Row10;"#},
16922        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16923        indoc! {r#"struct Row;
16924                   struct Row1;
16925                   struct Row1.1;
16926                   struct Row1.2;
16927                   struct Row2;ˇ
16928
16929                   struct Row4;
16930                   struct Row5;
16931                   struct Row6;
16932
16933                   struct Row8;
16934                   ˇstruct Row9;
16935                   struct Row9.1;
16936                   struct Row9.2;
16937                   struct Row9.3;
16938                   struct Row10;"#},
16939        base_text,
16940        &mut cx,
16941    );
16942    // Same for selections
16943    assert_hunk_revert(
16944        indoc! {r#"struct Row;
16945                   struct Row1;
16946                   struct Row2;
16947                   struct Row2.1;
16948                   struct Row2.2;
16949                   «ˇ
16950                   struct Row4;
16951                   struct» Row5;
16952                   «struct Row6;
16953                   ˇ»
16954                   struct Row9.1;
16955                   struct Row9.2;
16956                   struct Row9.3;
16957                   struct Row8;
16958                   struct Row9;
16959                   struct Row10;"#},
16960        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16961        indoc! {r#"struct Row;
16962                   struct Row1;
16963                   struct Row2;
16964                   struct Row2.1;
16965                   struct Row2.2;
16966                   «ˇ
16967                   struct Row4;
16968                   struct» Row5;
16969                   «struct Row6;
16970                   ˇ»
16971                   struct Row9.1;
16972                   struct Row9.2;
16973                   struct Row9.3;
16974                   struct Row8;
16975                   struct Row9;
16976                   struct Row10;"#},
16977        base_text,
16978        &mut cx,
16979    );
16980
16981    // When carets and selections intersect the addition hunks, those are reverted.
16982    // Adjacent carets got merged.
16983    assert_hunk_revert(
16984        indoc! {r#"struct Row;
16985                   ˇ// something on the top
16986                   struct Row1;
16987                   struct Row2;
16988                   struct Roˇw3.1;
16989                   struct Row2.2;
16990                   struct Row2.3;ˇ
16991
16992                   struct Row4;
16993                   struct ˇRow5.1;
16994                   struct Row5.2;
16995                   struct «Rowˇ»5.3;
16996                   struct Row5;
16997                   struct Row6;
16998                   ˇ
16999                   struct Row9.1;
17000                   struct «Rowˇ»9.2;
17001                   struct «ˇRow»9.3;
17002                   struct Row8;
17003                   struct Row9;
17004                   «ˇ// something on bottom»
17005                   struct Row10;"#},
17006        vec![
17007            DiffHunkStatusKind::Added,
17008            DiffHunkStatusKind::Added,
17009            DiffHunkStatusKind::Added,
17010            DiffHunkStatusKind::Added,
17011            DiffHunkStatusKind::Added,
17012        ],
17013        indoc! {r#"struct Row;
17014                   ˇstruct Row1;
17015                   struct Row2;
17016                   ˇ
17017                   struct Row4;
17018                   ˇstruct Row5;
17019                   struct Row6;
17020                   ˇ
17021                   ˇstruct Row8;
17022                   struct Row9;
17023                   ˇstruct Row10;"#},
17024        base_text,
17025        &mut cx,
17026    );
17027}
17028
17029#[gpui::test]
17030async fn test_modification_reverts(cx: &mut TestAppContext) {
17031    init_test(cx, |_| {});
17032    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17033    let base_text = indoc! {r#"
17034        struct Row;
17035        struct Row1;
17036        struct Row2;
17037
17038        struct Row4;
17039        struct Row5;
17040        struct Row6;
17041
17042        struct Row8;
17043        struct Row9;
17044        struct Row10;"#};
17045
17046    // Modification hunks behave the same as the addition ones.
17047    assert_hunk_revert(
17048        indoc! {r#"struct Row;
17049                   struct Row1;
17050                   struct Row33;
17051                   ˇ
17052                   struct Row4;
17053                   struct Row5;
17054                   struct Row6;
17055                   ˇ
17056                   struct Row99;
17057                   struct Row9;
17058                   struct Row10;"#},
17059        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17060        indoc! {r#"struct Row;
17061                   struct Row1;
17062                   struct Row33;
17063                   ˇ
17064                   struct Row4;
17065                   struct Row5;
17066                   struct Row6;
17067                   ˇ
17068                   struct Row99;
17069                   struct Row9;
17070                   struct Row10;"#},
17071        base_text,
17072        &mut cx,
17073    );
17074    assert_hunk_revert(
17075        indoc! {r#"struct Row;
17076                   struct Row1;
17077                   struct Row33;
17078                   «ˇ
17079                   struct Row4;
17080                   struct» Row5;
17081                   «struct Row6;
17082                   ˇ»
17083                   struct Row99;
17084                   struct Row9;
17085                   struct Row10;"#},
17086        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17087        indoc! {r#"struct Row;
17088                   struct Row1;
17089                   struct Row33;
17090                   «ˇ
17091                   struct Row4;
17092                   struct» Row5;
17093                   «struct Row6;
17094                   ˇ»
17095                   struct Row99;
17096                   struct Row9;
17097                   struct Row10;"#},
17098        base_text,
17099        &mut cx,
17100    );
17101
17102    assert_hunk_revert(
17103        indoc! {r#"ˇstruct Row1.1;
17104                   struct Row1;
17105                   «ˇstr»uct Row22;
17106
17107                   struct ˇRow44;
17108                   struct Row5;
17109                   struct «Rˇ»ow66;ˇ
17110
17111                   «struˇ»ct Row88;
17112                   struct Row9;
17113                   struct Row1011;ˇ"#},
17114        vec![
17115            DiffHunkStatusKind::Modified,
17116            DiffHunkStatusKind::Modified,
17117            DiffHunkStatusKind::Modified,
17118            DiffHunkStatusKind::Modified,
17119            DiffHunkStatusKind::Modified,
17120            DiffHunkStatusKind::Modified,
17121        ],
17122        indoc! {r#"struct Row;
17123                   ˇstruct Row1;
17124                   struct Row2;
17125                   ˇ
17126                   struct Row4;
17127                   ˇstruct Row5;
17128                   struct Row6;
17129                   ˇ
17130                   struct Row8;
17131                   ˇstruct Row9;
17132                   struct Row10;ˇ"#},
17133        base_text,
17134        &mut cx,
17135    );
17136}
17137
17138#[gpui::test]
17139async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17140    init_test(cx, |_| {});
17141    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17142    let base_text = indoc! {r#"
17143        one
17144
17145        two
17146        three
17147        "#};
17148
17149    cx.set_head_text(base_text);
17150    cx.set_state("\nˇ\n");
17151    cx.executor().run_until_parked();
17152    cx.update_editor(|editor, _window, cx| {
17153        editor.expand_selected_diff_hunks(cx);
17154    });
17155    cx.executor().run_until_parked();
17156    cx.update_editor(|editor, window, cx| {
17157        editor.backspace(&Default::default(), window, cx);
17158    });
17159    cx.run_until_parked();
17160    cx.assert_state_with_diff(
17161        indoc! {r#"
17162
17163        - two
17164        - threeˇ
17165        +
17166        "#}
17167        .to_string(),
17168    );
17169}
17170
17171#[gpui::test]
17172async fn test_deletion_reverts(cx: &mut TestAppContext) {
17173    init_test(cx, |_| {});
17174    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17175    let base_text = indoc! {r#"struct Row;
17176struct Row1;
17177struct Row2;
17178
17179struct Row4;
17180struct Row5;
17181struct Row6;
17182
17183struct Row8;
17184struct Row9;
17185struct Row10;"#};
17186
17187    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17188    assert_hunk_revert(
17189        indoc! {r#"struct Row;
17190                   struct Row2;
17191
17192                   ˇstruct Row4;
17193                   struct Row5;
17194                   struct Row6;
17195                   ˇ
17196                   struct Row8;
17197                   struct Row10;"#},
17198        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17199        indoc! {r#"struct Row;
17200                   struct Row2;
17201
17202                   ˇstruct Row4;
17203                   struct Row5;
17204                   struct Row6;
17205                   ˇ
17206                   struct Row8;
17207                   struct Row10;"#},
17208        base_text,
17209        &mut cx,
17210    );
17211    assert_hunk_revert(
17212        indoc! {r#"struct Row;
17213                   struct Row2;
17214
17215                   «ˇstruct Row4;
17216                   struct» Row5;
17217                   «struct Row6;
17218                   ˇ»
17219                   struct Row8;
17220                   struct Row10;"#},
17221        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17222        indoc! {r#"struct Row;
17223                   struct Row2;
17224
17225                   «ˇstruct Row4;
17226                   struct» Row5;
17227                   «struct Row6;
17228                   ˇ»
17229                   struct Row8;
17230                   struct Row10;"#},
17231        base_text,
17232        &mut cx,
17233    );
17234
17235    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17236    assert_hunk_revert(
17237        indoc! {r#"struct Row;
17238                   ˇstruct Row2;
17239
17240                   struct Row4;
17241                   struct Row5;
17242                   struct Row6;
17243
17244                   struct Row8;ˇ
17245                   struct Row10;"#},
17246        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17247        indoc! {r#"struct Row;
17248                   struct Row1;
17249                   ˇstruct Row2;
17250
17251                   struct Row4;
17252                   struct Row5;
17253                   struct Row6;
17254
17255                   struct Row8;ˇ
17256                   struct Row9;
17257                   struct Row10;"#},
17258        base_text,
17259        &mut cx,
17260    );
17261    assert_hunk_revert(
17262        indoc! {r#"struct Row;
17263                   struct Row2«ˇ;
17264                   struct Row4;
17265                   struct» Row5;
17266                   «struct Row6;
17267
17268                   struct Row8;ˇ»
17269                   struct Row10;"#},
17270        vec![
17271            DiffHunkStatusKind::Deleted,
17272            DiffHunkStatusKind::Deleted,
17273            DiffHunkStatusKind::Deleted,
17274        ],
17275        indoc! {r#"struct Row;
17276                   struct Row1;
17277                   struct Row2«ˇ;
17278
17279                   struct Row4;
17280                   struct» Row5;
17281                   «struct Row6;
17282
17283                   struct Row8;ˇ»
17284                   struct Row9;
17285                   struct Row10;"#},
17286        base_text,
17287        &mut cx,
17288    );
17289}
17290
17291#[gpui::test]
17292async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17293    init_test(cx, |_| {});
17294
17295    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17296    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17297    let base_text_3 =
17298        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17299
17300    let text_1 = edit_first_char_of_every_line(base_text_1);
17301    let text_2 = edit_first_char_of_every_line(base_text_2);
17302    let text_3 = edit_first_char_of_every_line(base_text_3);
17303
17304    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17305    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17306    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17307
17308    let multibuffer = cx.new(|cx| {
17309        let mut multibuffer = MultiBuffer::new(ReadWrite);
17310        multibuffer.push_excerpts(
17311            buffer_1.clone(),
17312            [
17313                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17314                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17315                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17316            ],
17317            cx,
17318        );
17319        multibuffer.push_excerpts(
17320            buffer_2.clone(),
17321            [
17322                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17323                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17324                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17325            ],
17326            cx,
17327        );
17328        multibuffer.push_excerpts(
17329            buffer_3.clone(),
17330            [
17331                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17332                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17333                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17334            ],
17335            cx,
17336        );
17337        multibuffer
17338    });
17339
17340    let fs = FakeFs::new(cx.executor());
17341    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17342    let (editor, cx) = cx
17343        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17344    editor.update_in(cx, |editor, _window, cx| {
17345        for (buffer, diff_base) in [
17346            (buffer_1.clone(), base_text_1),
17347            (buffer_2.clone(), base_text_2),
17348            (buffer_3.clone(), base_text_3),
17349        ] {
17350            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17351            editor
17352                .buffer
17353                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17354        }
17355    });
17356    cx.executor().run_until_parked();
17357
17358    editor.update_in(cx, |editor, window, cx| {
17359        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}");
17360        editor.select_all(&SelectAll, window, cx);
17361        editor.git_restore(&Default::default(), window, cx);
17362    });
17363    cx.executor().run_until_parked();
17364
17365    // When all ranges are selected, all buffer hunks are reverted.
17366    editor.update(cx, |editor, cx| {
17367        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");
17368    });
17369    buffer_1.update(cx, |buffer, _| {
17370        assert_eq!(buffer.text(), base_text_1);
17371    });
17372    buffer_2.update(cx, |buffer, _| {
17373        assert_eq!(buffer.text(), base_text_2);
17374    });
17375    buffer_3.update(cx, |buffer, _| {
17376        assert_eq!(buffer.text(), base_text_3);
17377    });
17378
17379    editor.update_in(cx, |editor, window, cx| {
17380        editor.undo(&Default::default(), window, cx);
17381    });
17382
17383    editor.update_in(cx, |editor, window, cx| {
17384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17385            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17386        });
17387        editor.git_restore(&Default::default(), window, cx);
17388    });
17389
17390    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17391    // but not affect buffer_2 and its related excerpts.
17392    editor.update(cx, |editor, cx| {
17393        assert_eq!(
17394            editor.text(cx),
17395            "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}"
17396        );
17397    });
17398    buffer_1.update(cx, |buffer, _| {
17399        assert_eq!(buffer.text(), base_text_1);
17400    });
17401    buffer_2.update(cx, |buffer, _| {
17402        assert_eq!(
17403            buffer.text(),
17404            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17405        );
17406    });
17407    buffer_3.update(cx, |buffer, _| {
17408        assert_eq!(
17409            buffer.text(),
17410            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17411        );
17412    });
17413
17414    fn edit_first_char_of_every_line(text: &str) -> String {
17415        text.split('\n')
17416            .map(|line| format!("X{}", &line[1..]))
17417            .collect::<Vec<_>>()
17418            .join("\n")
17419    }
17420}
17421
17422#[gpui::test]
17423async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17424    init_test(cx, |_| {});
17425
17426    let cols = 4;
17427    let rows = 10;
17428    let sample_text_1 = sample_text(rows, cols, 'a');
17429    assert_eq!(
17430        sample_text_1,
17431        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17432    );
17433    let sample_text_2 = sample_text(rows, cols, 'l');
17434    assert_eq!(
17435        sample_text_2,
17436        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17437    );
17438    let sample_text_3 = sample_text(rows, cols, 'v');
17439    assert_eq!(
17440        sample_text_3,
17441        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17442    );
17443
17444    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17445    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17446    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17447
17448    let multi_buffer = cx.new(|cx| {
17449        let mut multibuffer = MultiBuffer::new(ReadWrite);
17450        multibuffer.push_excerpts(
17451            buffer_1.clone(),
17452            [
17453                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17454                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17455                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17456            ],
17457            cx,
17458        );
17459        multibuffer.push_excerpts(
17460            buffer_2.clone(),
17461            [
17462                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17463                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17464                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17465            ],
17466            cx,
17467        );
17468        multibuffer.push_excerpts(
17469            buffer_3.clone(),
17470            [
17471                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17472                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17473                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17474            ],
17475            cx,
17476        );
17477        multibuffer
17478    });
17479
17480    let fs = FakeFs::new(cx.executor());
17481    fs.insert_tree(
17482        "/a",
17483        json!({
17484            "main.rs": sample_text_1,
17485            "other.rs": sample_text_2,
17486            "lib.rs": sample_text_3,
17487        }),
17488    )
17489    .await;
17490    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17491    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17492    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17493    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17494        Editor::new(
17495            EditorMode::full(),
17496            multi_buffer,
17497            Some(project.clone()),
17498            window,
17499            cx,
17500        )
17501    });
17502    let multibuffer_item_id = workspace
17503        .update(cx, |workspace, window, cx| {
17504            assert!(
17505                workspace.active_item(cx).is_none(),
17506                "active item should be None before the first item is added"
17507            );
17508            workspace.add_item_to_active_pane(
17509                Box::new(multi_buffer_editor.clone()),
17510                None,
17511                true,
17512                window,
17513                cx,
17514            );
17515            let active_item = workspace
17516                .active_item(cx)
17517                .expect("should have an active item after adding the multi buffer");
17518            assert!(
17519                !active_item.is_singleton(cx),
17520                "A multi buffer was expected to active after adding"
17521            );
17522            active_item.item_id()
17523        })
17524        .unwrap();
17525    cx.executor().run_until_parked();
17526
17527    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17528        editor.change_selections(
17529            SelectionEffects::scroll(Autoscroll::Next),
17530            window,
17531            cx,
17532            |s| s.select_ranges(Some(1..2)),
17533        );
17534        editor.open_excerpts(&OpenExcerpts, window, cx);
17535    });
17536    cx.executor().run_until_parked();
17537    let first_item_id = workspace
17538        .update(cx, |workspace, window, cx| {
17539            let active_item = workspace
17540                .active_item(cx)
17541                .expect("should have an active item after navigating into the 1st buffer");
17542            let first_item_id = active_item.item_id();
17543            assert_ne!(
17544                first_item_id, multibuffer_item_id,
17545                "Should navigate into the 1st buffer and activate it"
17546            );
17547            assert!(
17548                active_item.is_singleton(cx),
17549                "New active item should be a singleton buffer"
17550            );
17551            assert_eq!(
17552                active_item
17553                    .act_as::<Editor>(cx)
17554                    .expect("should have navigated into an editor for the 1st buffer")
17555                    .read(cx)
17556                    .text(cx),
17557                sample_text_1
17558            );
17559
17560            workspace
17561                .go_back(workspace.active_pane().downgrade(), window, cx)
17562                .detach_and_log_err(cx);
17563
17564            first_item_id
17565        })
17566        .unwrap();
17567    cx.executor().run_until_parked();
17568    workspace
17569        .update(cx, |workspace, _, cx| {
17570            let active_item = workspace
17571                .active_item(cx)
17572                .expect("should have an active item after navigating back");
17573            assert_eq!(
17574                active_item.item_id(),
17575                multibuffer_item_id,
17576                "Should navigate back to the multi buffer"
17577            );
17578            assert!(!active_item.is_singleton(cx));
17579        })
17580        .unwrap();
17581
17582    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17583        editor.change_selections(
17584            SelectionEffects::scroll(Autoscroll::Next),
17585            window,
17586            cx,
17587            |s| s.select_ranges(Some(39..40)),
17588        );
17589        editor.open_excerpts(&OpenExcerpts, window, cx);
17590    });
17591    cx.executor().run_until_parked();
17592    let second_item_id = workspace
17593        .update(cx, |workspace, window, cx| {
17594            let active_item = workspace
17595                .active_item(cx)
17596                .expect("should have an active item after navigating into the 2nd buffer");
17597            let second_item_id = active_item.item_id();
17598            assert_ne!(
17599                second_item_id, multibuffer_item_id,
17600                "Should navigate away from the multibuffer"
17601            );
17602            assert_ne!(
17603                second_item_id, first_item_id,
17604                "Should navigate into the 2nd buffer and activate it"
17605            );
17606            assert!(
17607                active_item.is_singleton(cx),
17608                "New active item should be a singleton buffer"
17609            );
17610            assert_eq!(
17611                active_item
17612                    .act_as::<Editor>(cx)
17613                    .expect("should have navigated into an editor")
17614                    .read(cx)
17615                    .text(cx),
17616                sample_text_2
17617            );
17618
17619            workspace
17620                .go_back(workspace.active_pane().downgrade(), window, cx)
17621                .detach_and_log_err(cx);
17622
17623            second_item_id
17624        })
17625        .unwrap();
17626    cx.executor().run_until_parked();
17627    workspace
17628        .update(cx, |workspace, _, cx| {
17629            let active_item = workspace
17630                .active_item(cx)
17631                .expect("should have an active item after navigating back from the 2nd buffer");
17632            assert_eq!(
17633                active_item.item_id(),
17634                multibuffer_item_id,
17635                "Should navigate back from the 2nd buffer to the multi buffer"
17636            );
17637            assert!(!active_item.is_singleton(cx));
17638        })
17639        .unwrap();
17640
17641    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17642        editor.change_selections(
17643            SelectionEffects::scroll(Autoscroll::Next),
17644            window,
17645            cx,
17646            |s| s.select_ranges(Some(70..70)),
17647        );
17648        editor.open_excerpts(&OpenExcerpts, window, cx);
17649    });
17650    cx.executor().run_until_parked();
17651    workspace
17652        .update(cx, |workspace, window, cx| {
17653            let active_item = workspace
17654                .active_item(cx)
17655                .expect("should have an active item after navigating into the 3rd buffer");
17656            let third_item_id = active_item.item_id();
17657            assert_ne!(
17658                third_item_id, multibuffer_item_id,
17659                "Should navigate into the 3rd buffer and activate it"
17660            );
17661            assert_ne!(third_item_id, first_item_id);
17662            assert_ne!(third_item_id, second_item_id);
17663            assert!(
17664                active_item.is_singleton(cx),
17665                "New active item should be a singleton buffer"
17666            );
17667            assert_eq!(
17668                active_item
17669                    .act_as::<Editor>(cx)
17670                    .expect("should have navigated into an editor")
17671                    .read(cx)
17672                    .text(cx),
17673                sample_text_3
17674            );
17675
17676            workspace
17677                .go_back(workspace.active_pane().downgrade(), window, cx)
17678                .detach_and_log_err(cx);
17679        })
17680        .unwrap();
17681    cx.executor().run_until_parked();
17682    workspace
17683        .update(cx, |workspace, _, cx| {
17684            let active_item = workspace
17685                .active_item(cx)
17686                .expect("should have an active item after navigating back from the 3rd buffer");
17687            assert_eq!(
17688                active_item.item_id(),
17689                multibuffer_item_id,
17690                "Should navigate back from the 3rd buffer to the multi buffer"
17691            );
17692            assert!(!active_item.is_singleton(cx));
17693        })
17694        .unwrap();
17695}
17696
17697#[gpui::test]
17698async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17699    init_test(cx, |_| {});
17700
17701    let mut cx = EditorTestContext::new(cx).await;
17702
17703    let diff_base = r#"
17704        use some::mod;
17705
17706        const A: u32 = 42;
17707
17708        fn main() {
17709            println!("hello");
17710
17711            println!("world");
17712        }
17713        "#
17714    .unindent();
17715
17716    cx.set_state(
17717        &r#"
17718        use some::modified;
17719
17720        ˇ
17721        fn main() {
17722            println!("hello there");
17723
17724            println!("around the");
17725            println!("world");
17726        }
17727        "#
17728        .unindent(),
17729    );
17730
17731    cx.set_head_text(&diff_base);
17732    executor.run_until_parked();
17733
17734    cx.update_editor(|editor, window, cx| {
17735        editor.go_to_next_hunk(&GoToHunk, window, cx);
17736        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17737    });
17738    executor.run_until_parked();
17739    cx.assert_state_with_diff(
17740        r#"
17741          use some::modified;
17742
17743
17744          fn main() {
17745        -     println!("hello");
17746        + ˇ    println!("hello there");
17747
17748              println!("around the");
17749              println!("world");
17750          }
17751        "#
17752        .unindent(),
17753    );
17754
17755    cx.update_editor(|editor, window, cx| {
17756        for _ in 0..2 {
17757            editor.go_to_next_hunk(&GoToHunk, window, cx);
17758            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17759        }
17760    });
17761    executor.run_until_parked();
17762    cx.assert_state_with_diff(
17763        r#"
17764        - use some::mod;
17765        + ˇuse some::modified;
17766
17767
17768          fn main() {
17769        -     println!("hello");
17770        +     println!("hello there");
17771
17772        +     println!("around the");
17773              println!("world");
17774          }
17775        "#
17776        .unindent(),
17777    );
17778
17779    cx.update_editor(|editor, window, cx| {
17780        editor.go_to_next_hunk(&GoToHunk, window, cx);
17781        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17782    });
17783    executor.run_until_parked();
17784    cx.assert_state_with_diff(
17785        r#"
17786        - use some::mod;
17787        + use some::modified;
17788
17789        - const A: u32 = 42;
17790          ˇ
17791          fn main() {
17792        -     println!("hello");
17793        +     println!("hello there");
17794
17795        +     println!("around the");
17796              println!("world");
17797          }
17798        "#
17799        .unindent(),
17800    );
17801
17802    cx.update_editor(|editor, window, cx| {
17803        editor.cancel(&Cancel, window, cx);
17804    });
17805
17806    cx.assert_state_with_diff(
17807        r#"
17808          use some::modified;
17809
17810          ˇ
17811          fn main() {
17812              println!("hello there");
17813
17814              println!("around the");
17815              println!("world");
17816          }
17817        "#
17818        .unindent(),
17819    );
17820}
17821
17822#[gpui::test]
17823async fn test_diff_base_change_with_expanded_diff_hunks(
17824    executor: BackgroundExecutor,
17825    cx: &mut TestAppContext,
17826) {
17827    init_test(cx, |_| {});
17828
17829    let mut cx = EditorTestContext::new(cx).await;
17830
17831    let diff_base = r#"
17832        use some::mod1;
17833        use some::mod2;
17834
17835        const A: u32 = 42;
17836        const B: u32 = 42;
17837        const C: u32 = 42;
17838
17839        fn main() {
17840            println!("hello");
17841
17842            println!("world");
17843        }
17844        "#
17845    .unindent();
17846
17847    cx.set_state(
17848        &r#"
17849        use some::mod2;
17850
17851        const A: u32 = 42;
17852        const C: u32 = 42;
17853
17854        fn main(ˇ) {
17855            //println!("hello");
17856
17857            println!("world");
17858            //
17859            //
17860        }
17861        "#
17862        .unindent(),
17863    );
17864
17865    cx.set_head_text(&diff_base);
17866    executor.run_until_parked();
17867
17868    cx.update_editor(|editor, window, cx| {
17869        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17870    });
17871    executor.run_until_parked();
17872    cx.assert_state_with_diff(
17873        r#"
17874        - use some::mod1;
17875          use some::mod2;
17876
17877          const A: u32 = 42;
17878        - const B: u32 = 42;
17879          const C: u32 = 42;
17880
17881          fn main(ˇ) {
17882        -     println!("hello");
17883        +     //println!("hello");
17884
17885              println!("world");
17886        +     //
17887        +     //
17888          }
17889        "#
17890        .unindent(),
17891    );
17892
17893    cx.set_head_text("new diff base!");
17894    executor.run_until_parked();
17895    cx.assert_state_with_diff(
17896        r#"
17897        - new diff base!
17898        + use some::mod2;
17899        +
17900        + const A: u32 = 42;
17901        + const C: u32 = 42;
17902        +
17903        + fn main(ˇ) {
17904        +     //println!("hello");
17905        +
17906        +     println!("world");
17907        +     //
17908        +     //
17909        + }
17910        "#
17911        .unindent(),
17912    );
17913}
17914
17915#[gpui::test]
17916async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17917    init_test(cx, |_| {});
17918
17919    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17920    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17921    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17922    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17923    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17924    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17925
17926    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17927    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17928    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17929
17930    let multi_buffer = cx.new(|cx| {
17931        let mut multibuffer = MultiBuffer::new(ReadWrite);
17932        multibuffer.push_excerpts(
17933            buffer_1.clone(),
17934            [
17935                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17936                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17937                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17938            ],
17939            cx,
17940        );
17941        multibuffer.push_excerpts(
17942            buffer_2.clone(),
17943            [
17944                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17945                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17946                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17947            ],
17948            cx,
17949        );
17950        multibuffer.push_excerpts(
17951            buffer_3.clone(),
17952            [
17953                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17954                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17955                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17956            ],
17957            cx,
17958        );
17959        multibuffer
17960    });
17961
17962    let editor =
17963        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17964    editor
17965        .update(cx, |editor, _window, cx| {
17966            for (buffer, diff_base) in [
17967                (buffer_1.clone(), file_1_old),
17968                (buffer_2.clone(), file_2_old),
17969                (buffer_3.clone(), file_3_old),
17970            ] {
17971                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17972                editor
17973                    .buffer
17974                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17975            }
17976        })
17977        .unwrap();
17978
17979    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17980    cx.run_until_parked();
17981
17982    cx.assert_editor_state(
17983        &"
17984            ˇaaa
17985            ccc
17986            ddd
17987
17988            ggg
17989            hhh
17990
17991
17992            lll
17993            mmm
17994            NNN
17995
17996            qqq
17997            rrr
17998
17999            uuu
18000            111
18001            222
18002            333
18003
18004            666
18005            777
18006
18007            000
18008            !!!"
18009        .unindent(),
18010    );
18011
18012    cx.update_editor(|editor, window, cx| {
18013        editor.select_all(&SelectAll, window, cx);
18014        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18015    });
18016    cx.executor().run_until_parked();
18017
18018    cx.assert_state_with_diff(
18019        "
18020            «aaa
18021          - bbb
18022            ccc
18023            ddd
18024
18025            ggg
18026            hhh
18027
18028
18029            lll
18030            mmm
18031          - nnn
18032          + NNN
18033
18034            qqq
18035            rrr
18036
18037            uuu
18038            111
18039            222
18040            333
18041
18042          + 666
18043            777
18044
18045            000
18046            !!!ˇ»"
18047            .unindent(),
18048    );
18049}
18050
18051#[gpui::test]
18052async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18053    init_test(cx, |_| {});
18054
18055    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18056    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18057
18058    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18059    let multi_buffer = cx.new(|cx| {
18060        let mut multibuffer = MultiBuffer::new(ReadWrite);
18061        multibuffer.push_excerpts(
18062            buffer.clone(),
18063            [
18064                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18065                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18066                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18067            ],
18068            cx,
18069        );
18070        multibuffer
18071    });
18072
18073    let editor =
18074        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18075    editor
18076        .update(cx, |editor, _window, cx| {
18077            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18078            editor
18079                .buffer
18080                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18081        })
18082        .unwrap();
18083
18084    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18085    cx.run_until_parked();
18086
18087    cx.update_editor(|editor, window, cx| {
18088        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18089    });
18090    cx.executor().run_until_parked();
18091
18092    // When the start of a hunk coincides with the start of its excerpt,
18093    // the hunk is expanded. When the start of a a hunk is earlier than
18094    // the start of its excerpt, the hunk is not expanded.
18095    cx.assert_state_with_diff(
18096        "
18097            ˇaaa
18098          - bbb
18099          + BBB
18100
18101          - ddd
18102          - eee
18103          + DDD
18104          + EEE
18105            fff
18106
18107            iii
18108        "
18109        .unindent(),
18110    );
18111}
18112
18113#[gpui::test]
18114async fn test_edits_around_expanded_insertion_hunks(
18115    executor: BackgroundExecutor,
18116    cx: &mut TestAppContext,
18117) {
18118    init_test(cx, |_| {});
18119
18120    let mut cx = EditorTestContext::new(cx).await;
18121
18122    let diff_base = r#"
18123        use some::mod1;
18124        use some::mod2;
18125
18126        const A: u32 = 42;
18127
18128        fn main() {
18129            println!("hello");
18130
18131            println!("world");
18132        }
18133        "#
18134    .unindent();
18135    executor.run_until_parked();
18136    cx.set_state(
18137        &r#"
18138        use some::mod1;
18139        use some::mod2;
18140
18141        const A: u32 = 42;
18142        const B: u32 = 42;
18143        const C: u32 = 42;
18144        ˇ
18145
18146        fn main() {
18147            println!("hello");
18148
18149            println!("world");
18150        }
18151        "#
18152        .unindent(),
18153    );
18154
18155    cx.set_head_text(&diff_base);
18156    executor.run_until_parked();
18157
18158    cx.update_editor(|editor, window, cx| {
18159        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18160    });
18161    executor.run_until_parked();
18162
18163    cx.assert_state_with_diff(
18164        r#"
18165        use some::mod1;
18166        use some::mod2;
18167
18168        const A: u32 = 42;
18169      + const B: u32 = 42;
18170      + const C: u32 = 42;
18171      + ˇ
18172
18173        fn main() {
18174            println!("hello");
18175
18176            println!("world");
18177        }
18178      "#
18179        .unindent(),
18180    );
18181
18182    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18183    executor.run_until_parked();
18184
18185    cx.assert_state_with_diff(
18186        r#"
18187        use some::mod1;
18188        use some::mod2;
18189
18190        const A: u32 = 42;
18191      + const B: u32 = 42;
18192      + const C: u32 = 42;
18193      + const D: u32 = 42;
18194      + ˇ
18195
18196        fn main() {
18197            println!("hello");
18198
18199            println!("world");
18200        }
18201      "#
18202        .unindent(),
18203    );
18204
18205    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18206    executor.run_until_parked();
18207
18208    cx.assert_state_with_diff(
18209        r#"
18210        use some::mod1;
18211        use some::mod2;
18212
18213        const A: u32 = 42;
18214      + const B: u32 = 42;
18215      + const C: u32 = 42;
18216      + const D: u32 = 42;
18217      + const E: u32 = 42;
18218      + ˇ
18219
18220        fn main() {
18221            println!("hello");
18222
18223            println!("world");
18224        }
18225      "#
18226        .unindent(),
18227    );
18228
18229    cx.update_editor(|editor, window, cx| {
18230        editor.delete_line(&DeleteLine, window, cx);
18231    });
18232    executor.run_until_parked();
18233
18234    cx.assert_state_with_diff(
18235        r#"
18236        use some::mod1;
18237        use some::mod2;
18238
18239        const A: u32 = 42;
18240      + const B: u32 = 42;
18241      + const C: u32 = 42;
18242      + const D: u32 = 42;
18243      + const E: u32 = 42;
18244        ˇ
18245        fn main() {
18246            println!("hello");
18247
18248            println!("world");
18249        }
18250      "#
18251        .unindent(),
18252    );
18253
18254    cx.update_editor(|editor, window, cx| {
18255        editor.move_up(&MoveUp, window, cx);
18256        editor.delete_line(&DeleteLine, window, cx);
18257        editor.move_up(&MoveUp, window, cx);
18258        editor.delete_line(&DeleteLine, window, cx);
18259        editor.move_up(&MoveUp, window, cx);
18260        editor.delete_line(&DeleteLine, window, cx);
18261    });
18262    executor.run_until_parked();
18263    cx.assert_state_with_diff(
18264        r#"
18265        use some::mod1;
18266        use some::mod2;
18267
18268        const A: u32 = 42;
18269      + const B: u32 = 42;
18270        ˇ
18271        fn main() {
18272            println!("hello");
18273
18274            println!("world");
18275        }
18276      "#
18277        .unindent(),
18278    );
18279
18280    cx.update_editor(|editor, window, cx| {
18281        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18282        editor.delete_line(&DeleteLine, window, cx);
18283    });
18284    executor.run_until_parked();
18285    cx.assert_state_with_diff(
18286        r#"
18287        ˇ
18288        fn main() {
18289            println!("hello");
18290
18291            println!("world");
18292        }
18293      "#
18294        .unindent(),
18295    );
18296}
18297
18298#[gpui::test]
18299async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18300    init_test(cx, |_| {});
18301
18302    let mut cx = EditorTestContext::new(cx).await;
18303    cx.set_head_text(indoc! { "
18304        one
18305        two
18306        three
18307        four
18308        five
18309        "
18310    });
18311    cx.set_state(indoc! { "
18312        one
18313        ˇthree
18314        five
18315    "});
18316    cx.run_until_parked();
18317    cx.update_editor(|editor, window, cx| {
18318        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18319    });
18320    cx.assert_state_with_diff(
18321        indoc! { "
18322        one
18323      - two
18324        ˇthree
18325      - four
18326        five
18327    "}
18328        .to_string(),
18329    );
18330    cx.update_editor(|editor, window, cx| {
18331        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18332    });
18333
18334    cx.assert_state_with_diff(
18335        indoc! { "
18336        one
18337        ˇthree
18338        five
18339    "}
18340        .to_string(),
18341    );
18342
18343    cx.set_state(indoc! { "
18344        one
18345        ˇTWO
18346        three
18347        four
18348        five
18349    "});
18350    cx.run_until_parked();
18351    cx.update_editor(|editor, window, cx| {
18352        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18353    });
18354
18355    cx.assert_state_with_diff(
18356        indoc! { "
18357            one
18358          - two
18359          + ˇTWO
18360            three
18361            four
18362            five
18363        "}
18364        .to_string(),
18365    );
18366    cx.update_editor(|editor, window, cx| {
18367        editor.move_up(&Default::default(), window, cx);
18368        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18369    });
18370    cx.assert_state_with_diff(
18371        indoc! { "
18372            one
18373            ˇTWO
18374            three
18375            four
18376            five
18377        "}
18378        .to_string(),
18379    );
18380}
18381
18382#[gpui::test]
18383async fn test_edits_around_expanded_deletion_hunks(
18384    executor: BackgroundExecutor,
18385    cx: &mut TestAppContext,
18386) {
18387    init_test(cx, |_| {});
18388
18389    let mut cx = EditorTestContext::new(cx).await;
18390
18391    let diff_base = r#"
18392        use some::mod1;
18393        use some::mod2;
18394
18395        const A: u32 = 42;
18396        const B: u32 = 42;
18397        const C: u32 = 42;
18398
18399
18400        fn main() {
18401            println!("hello");
18402
18403            println!("world");
18404        }
18405    "#
18406    .unindent();
18407    executor.run_until_parked();
18408    cx.set_state(
18409        &r#"
18410        use some::mod1;
18411        use some::mod2;
18412
18413        ˇconst B: u32 = 42;
18414        const C: u32 = 42;
18415
18416
18417        fn main() {
18418            println!("hello");
18419
18420            println!("world");
18421        }
18422        "#
18423        .unindent(),
18424    );
18425
18426    cx.set_head_text(&diff_base);
18427    executor.run_until_parked();
18428
18429    cx.update_editor(|editor, window, cx| {
18430        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18431    });
18432    executor.run_until_parked();
18433
18434    cx.assert_state_with_diff(
18435        r#"
18436        use some::mod1;
18437        use some::mod2;
18438
18439      - const A: u32 = 42;
18440        ˇconst B: u32 = 42;
18441        const C: u32 = 42;
18442
18443
18444        fn main() {
18445            println!("hello");
18446
18447            println!("world");
18448        }
18449      "#
18450        .unindent(),
18451    );
18452
18453    cx.update_editor(|editor, window, cx| {
18454        editor.delete_line(&DeleteLine, window, cx);
18455    });
18456    executor.run_until_parked();
18457    cx.assert_state_with_diff(
18458        r#"
18459        use some::mod1;
18460        use some::mod2;
18461
18462      - const A: u32 = 42;
18463      - const B: u32 = 42;
18464        ˇconst C: u32 = 42;
18465
18466
18467        fn main() {
18468            println!("hello");
18469
18470            println!("world");
18471        }
18472      "#
18473        .unindent(),
18474    );
18475
18476    cx.update_editor(|editor, window, cx| {
18477        editor.delete_line(&DeleteLine, window, cx);
18478    });
18479    executor.run_until_parked();
18480    cx.assert_state_with_diff(
18481        r#"
18482        use some::mod1;
18483        use some::mod2;
18484
18485      - const A: u32 = 42;
18486      - const B: u32 = 42;
18487      - const C: u32 = 42;
18488        ˇ
18489
18490        fn main() {
18491            println!("hello");
18492
18493            println!("world");
18494        }
18495      "#
18496        .unindent(),
18497    );
18498
18499    cx.update_editor(|editor, window, cx| {
18500        editor.handle_input("replacement", window, cx);
18501    });
18502    executor.run_until_parked();
18503    cx.assert_state_with_diff(
18504        r#"
18505        use some::mod1;
18506        use some::mod2;
18507
18508      - const A: u32 = 42;
18509      - const B: u32 = 42;
18510      - const C: u32 = 42;
18511      -
18512      + replacementˇ
18513
18514        fn main() {
18515            println!("hello");
18516
18517            println!("world");
18518        }
18519      "#
18520        .unindent(),
18521    );
18522}
18523
18524#[gpui::test]
18525async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18526    init_test(cx, |_| {});
18527
18528    let mut cx = EditorTestContext::new(cx).await;
18529
18530    let base_text = r#"
18531        one
18532        two
18533        three
18534        four
18535        five
18536    "#
18537    .unindent();
18538    executor.run_until_parked();
18539    cx.set_state(
18540        &r#"
18541        one
18542        two
18543        fˇour
18544        five
18545        "#
18546        .unindent(),
18547    );
18548
18549    cx.set_head_text(&base_text);
18550    executor.run_until_parked();
18551
18552    cx.update_editor(|editor, window, cx| {
18553        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18554    });
18555    executor.run_until_parked();
18556
18557    cx.assert_state_with_diff(
18558        r#"
18559          one
18560          two
18561        - three
18562          fˇour
18563          five
18564        "#
18565        .unindent(),
18566    );
18567
18568    cx.update_editor(|editor, window, cx| {
18569        editor.backspace(&Backspace, window, cx);
18570        editor.backspace(&Backspace, window, cx);
18571    });
18572    executor.run_until_parked();
18573    cx.assert_state_with_diff(
18574        r#"
18575          one
18576          two
18577        - threeˇ
18578        - four
18579        + our
18580          five
18581        "#
18582        .unindent(),
18583    );
18584}
18585
18586#[gpui::test]
18587async fn test_edit_after_expanded_modification_hunk(
18588    executor: BackgroundExecutor,
18589    cx: &mut TestAppContext,
18590) {
18591    init_test(cx, |_| {});
18592
18593    let mut cx = EditorTestContext::new(cx).await;
18594
18595    let diff_base = r#"
18596        use some::mod1;
18597        use some::mod2;
18598
18599        const A: u32 = 42;
18600        const B: u32 = 42;
18601        const C: u32 = 42;
18602        const D: u32 = 42;
18603
18604
18605        fn main() {
18606            println!("hello");
18607
18608            println!("world");
18609        }"#
18610    .unindent();
18611
18612    cx.set_state(
18613        &r#"
18614        use some::mod1;
18615        use some::mod2;
18616
18617        const A: u32 = 42;
18618        const B: u32 = 42;
18619        const C: u32 = 43ˇ
18620        const D: u32 = 42;
18621
18622
18623        fn main() {
18624            println!("hello");
18625
18626            println!("world");
18627        }"#
18628        .unindent(),
18629    );
18630
18631    cx.set_head_text(&diff_base);
18632    executor.run_until_parked();
18633    cx.update_editor(|editor, window, cx| {
18634        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18635    });
18636    executor.run_until_parked();
18637
18638    cx.assert_state_with_diff(
18639        r#"
18640        use some::mod1;
18641        use some::mod2;
18642
18643        const A: u32 = 42;
18644        const B: u32 = 42;
18645      - const C: u32 = 42;
18646      + const C: u32 = 43ˇ
18647        const D: u32 = 42;
18648
18649
18650        fn main() {
18651            println!("hello");
18652
18653            println!("world");
18654        }"#
18655        .unindent(),
18656    );
18657
18658    cx.update_editor(|editor, window, cx| {
18659        editor.handle_input("\nnew_line\n", window, cx);
18660    });
18661    executor.run_until_parked();
18662
18663    cx.assert_state_with_diff(
18664        r#"
18665        use some::mod1;
18666        use some::mod2;
18667
18668        const A: u32 = 42;
18669        const B: u32 = 42;
18670      - const C: u32 = 42;
18671      + const C: u32 = 43
18672      + new_line
18673      + ˇ
18674        const D: u32 = 42;
18675
18676
18677        fn main() {
18678            println!("hello");
18679
18680            println!("world");
18681        }"#
18682        .unindent(),
18683    );
18684}
18685
18686#[gpui::test]
18687async fn test_stage_and_unstage_added_file_hunk(
18688    executor: BackgroundExecutor,
18689    cx: &mut TestAppContext,
18690) {
18691    init_test(cx, |_| {});
18692
18693    let mut cx = EditorTestContext::new(cx).await;
18694    cx.update_editor(|editor, _, cx| {
18695        editor.set_expand_all_diff_hunks(cx);
18696    });
18697
18698    let working_copy = r#"
18699            ˇfn main() {
18700                println!("hello, world!");
18701            }
18702        "#
18703    .unindent();
18704
18705    cx.set_state(&working_copy);
18706    executor.run_until_parked();
18707
18708    cx.assert_state_with_diff(
18709        r#"
18710            + ˇfn main() {
18711            +     println!("hello, world!");
18712            + }
18713        "#
18714        .unindent(),
18715    );
18716    cx.assert_index_text(None);
18717
18718    cx.update_editor(|editor, window, cx| {
18719        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18720    });
18721    executor.run_until_parked();
18722    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18723    cx.assert_state_with_diff(
18724        r#"
18725            + ˇfn main() {
18726            +     println!("hello, world!");
18727            + }
18728        "#
18729        .unindent(),
18730    );
18731
18732    cx.update_editor(|editor, window, cx| {
18733        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18734    });
18735    executor.run_until_parked();
18736    cx.assert_index_text(None);
18737}
18738
18739async fn setup_indent_guides_editor(
18740    text: &str,
18741    cx: &mut TestAppContext,
18742) -> (BufferId, EditorTestContext) {
18743    init_test(cx, |_| {});
18744
18745    let mut cx = EditorTestContext::new(cx).await;
18746
18747    let buffer_id = cx.update_editor(|editor, window, cx| {
18748        editor.set_text(text, window, cx);
18749        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18750
18751        buffer_ids[0]
18752    });
18753
18754    (buffer_id, cx)
18755}
18756
18757fn assert_indent_guides(
18758    range: Range<u32>,
18759    expected: Vec<IndentGuide>,
18760    active_indices: Option<Vec<usize>>,
18761    cx: &mut EditorTestContext,
18762) {
18763    let indent_guides = cx.update_editor(|editor, window, cx| {
18764        let snapshot = editor.snapshot(window, cx).display_snapshot;
18765        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18766            editor,
18767            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18768            true,
18769            &snapshot,
18770            cx,
18771        );
18772
18773        indent_guides.sort_by(|a, b| {
18774            a.depth.cmp(&b.depth).then(
18775                a.start_row
18776                    .cmp(&b.start_row)
18777                    .then(a.end_row.cmp(&b.end_row)),
18778            )
18779        });
18780        indent_guides
18781    });
18782
18783    if let Some(expected) = active_indices {
18784        let active_indices = cx.update_editor(|editor, window, cx| {
18785            let snapshot = editor.snapshot(window, cx).display_snapshot;
18786            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18787        });
18788
18789        assert_eq!(
18790            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18791            expected,
18792            "Active indent guide indices do not match"
18793        );
18794    }
18795
18796    assert_eq!(indent_guides, expected, "Indent guides do not match");
18797}
18798
18799fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18800    IndentGuide {
18801        buffer_id,
18802        start_row: MultiBufferRow(start_row),
18803        end_row: MultiBufferRow(end_row),
18804        depth,
18805        tab_size: 4,
18806        settings: IndentGuideSettings {
18807            enabled: true,
18808            line_width: 1,
18809            active_line_width: 1,
18810            ..Default::default()
18811        },
18812    }
18813}
18814
18815#[gpui::test]
18816async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18817    let (buffer_id, mut cx) = setup_indent_guides_editor(
18818        &"
18819        fn main() {
18820            let a = 1;
18821        }"
18822        .unindent(),
18823        cx,
18824    )
18825    .await;
18826
18827    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18828}
18829
18830#[gpui::test]
18831async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18832    let (buffer_id, mut cx) = setup_indent_guides_editor(
18833        &"
18834        fn main() {
18835            let a = 1;
18836            let b = 2;
18837        }"
18838        .unindent(),
18839        cx,
18840    )
18841    .await;
18842
18843    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18844}
18845
18846#[gpui::test]
18847async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18848    let (buffer_id, mut cx) = setup_indent_guides_editor(
18849        &"
18850        fn main() {
18851            let a = 1;
18852            if a == 3 {
18853                let b = 2;
18854            } else {
18855                let c = 3;
18856            }
18857        }"
18858        .unindent(),
18859        cx,
18860    )
18861    .await;
18862
18863    assert_indent_guides(
18864        0..8,
18865        vec![
18866            indent_guide(buffer_id, 1, 6, 0),
18867            indent_guide(buffer_id, 3, 3, 1),
18868            indent_guide(buffer_id, 5, 5, 1),
18869        ],
18870        None,
18871        &mut cx,
18872    );
18873}
18874
18875#[gpui::test]
18876async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18877    let (buffer_id, mut cx) = setup_indent_guides_editor(
18878        &"
18879        fn main() {
18880            let a = 1;
18881                let b = 2;
18882            let c = 3;
18883        }"
18884        .unindent(),
18885        cx,
18886    )
18887    .await;
18888
18889    assert_indent_guides(
18890        0..5,
18891        vec![
18892            indent_guide(buffer_id, 1, 3, 0),
18893            indent_guide(buffer_id, 2, 2, 1),
18894        ],
18895        None,
18896        &mut cx,
18897    );
18898}
18899
18900#[gpui::test]
18901async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18902    let (buffer_id, mut cx) = setup_indent_guides_editor(
18903        &"
18904        fn main() {
18905            let a = 1;
18906
18907            let c = 3;
18908        }"
18909        .unindent(),
18910        cx,
18911    )
18912    .await;
18913
18914    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18915}
18916
18917#[gpui::test]
18918async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18919    let (buffer_id, mut cx) = setup_indent_guides_editor(
18920        &"
18921        fn main() {
18922            let a = 1;
18923
18924            let c = 3;
18925
18926            if a == 3 {
18927                let b = 2;
18928            } else {
18929                let c = 3;
18930            }
18931        }"
18932        .unindent(),
18933        cx,
18934    )
18935    .await;
18936
18937    assert_indent_guides(
18938        0..11,
18939        vec![
18940            indent_guide(buffer_id, 1, 9, 0),
18941            indent_guide(buffer_id, 6, 6, 1),
18942            indent_guide(buffer_id, 8, 8, 1),
18943        ],
18944        None,
18945        &mut cx,
18946    );
18947}
18948
18949#[gpui::test]
18950async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18951    let (buffer_id, mut cx) = setup_indent_guides_editor(
18952        &"
18953        fn main() {
18954            let a = 1;
18955
18956            let c = 3;
18957
18958            if a == 3 {
18959                let b = 2;
18960            } else {
18961                let c = 3;
18962            }
18963        }"
18964        .unindent(),
18965        cx,
18966    )
18967    .await;
18968
18969    assert_indent_guides(
18970        1..11,
18971        vec![
18972            indent_guide(buffer_id, 1, 9, 0),
18973            indent_guide(buffer_id, 6, 6, 1),
18974            indent_guide(buffer_id, 8, 8, 1),
18975        ],
18976        None,
18977        &mut cx,
18978    );
18979}
18980
18981#[gpui::test]
18982async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18983    let (buffer_id, mut cx) = setup_indent_guides_editor(
18984        &"
18985        fn main() {
18986            let a = 1;
18987
18988            let c = 3;
18989
18990            if a == 3 {
18991                let b = 2;
18992            } else {
18993                let c = 3;
18994            }
18995        }"
18996        .unindent(),
18997        cx,
18998    )
18999    .await;
19000
19001    assert_indent_guides(
19002        1..10,
19003        vec![
19004            indent_guide(buffer_id, 1, 9, 0),
19005            indent_guide(buffer_id, 6, 6, 1),
19006            indent_guide(buffer_id, 8, 8, 1),
19007        ],
19008        None,
19009        &mut cx,
19010    );
19011}
19012
19013#[gpui::test]
19014async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19015    let (buffer_id, mut cx) = setup_indent_guides_editor(
19016        &"
19017        fn main() {
19018            if a {
19019                b(
19020                    c,
19021                    d,
19022                )
19023            } else {
19024                e(
19025                    f
19026                )
19027            }
19028        }"
19029        .unindent(),
19030        cx,
19031    )
19032    .await;
19033
19034    assert_indent_guides(
19035        0..11,
19036        vec![
19037            indent_guide(buffer_id, 1, 10, 0),
19038            indent_guide(buffer_id, 2, 5, 1),
19039            indent_guide(buffer_id, 7, 9, 1),
19040            indent_guide(buffer_id, 3, 4, 2),
19041            indent_guide(buffer_id, 8, 8, 2),
19042        ],
19043        None,
19044        &mut cx,
19045    );
19046
19047    cx.update_editor(|editor, window, cx| {
19048        editor.fold_at(MultiBufferRow(2), window, cx);
19049        assert_eq!(
19050            editor.display_text(cx),
19051            "
19052            fn main() {
19053                if a {
19054                    b(⋯
19055                    )
19056                } else {
19057                    e(
19058                        f
19059                    )
19060                }
19061            }"
19062            .unindent()
19063        );
19064    });
19065
19066    assert_indent_guides(
19067        0..11,
19068        vec![
19069            indent_guide(buffer_id, 1, 10, 0),
19070            indent_guide(buffer_id, 2, 5, 1),
19071            indent_guide(buffer_id, 7, 9, 1),
19072            indent_guide(buffer_id, 8, 8, 2),
19073        ],
19074        None,
19075        &mut cx,
19076    );
19077}
19078
19079#[gpui::test]
19080async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19081    let (buffer_id, mut cx) = setup_indent_guides_editor(
19082        &"
19083        block1
19084            block2
19085                block3
19086                    block4
19087            block2
19088        block1
19089        block1"
19090            .unindent(),
19091        cx,
19092    )
19093    .await;
19094
19095    assert_indent_guides(
19096        1..10,
19097        vec![
19098            indent_guide(buffer_id, 1, 4, 0),
19099            indent_guide(buffer_id, 2, 3, 1),
19100            indent_guide(buffer_id, 3, 3, 2),
19101        ],
19102        None,
19103        &mut cx,
19104    );
19105}
19106
19107#[gpui::test]
19108async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19109    let (buffer_id, mut cx) = setup_indent_guides_editor(
19110        &"
19111        block1
19112            block2
19113                block3
19114
19115        block1
19116        block1"
19117            .unindent(),
19118        cx,
19119    )
19120    .await;
19121
19122    assert_indent_guides(
19123        0..6,
19124        vec![
19125            indent_guide(buffer_id, 1, 2, 0),
19126            indent_guide(buffer_id, 2, 2, 1),
19127        ],
19128        None,
19129        &mut cx,
19130    );
19131}
19132
19133#[gpui::test]
19134async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19135    let (buffer_id, mut cx) = setup_indent_guides_editor(
19136        &"
19137        function component() {
19138        \treturn (
19139        \t\t\t
19140        \t\t<div>
19141        \t\t\t<abc></abc>
19142        \t\t</div>
19143        \t)
19144        }"
19145        .unindent(),
19146        cx,
19147    )
19148    .await;
19149
19150    assert_indent_guides(
19151        0..8,
19152        vec![
19153            indent_guide(buffer_id, 1, 6, 0),
19154            indent_guide(buffer_id, 2, 5, 1),
19155            indent_guide(buffer_id, 4, 4, 2),
19156        ],
19157        None,
19158        &mut cx,
19159    );
19160}
19161
19162#[gpui::test]
19163async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19164    let (buffer_id, mut cx) = setup_indent_guides_editor(
19165        &"
19166        function component() {
19167        \treturn (
19168        \t
19169        \t\t<div>
19170        \t\t\t<abc></abc>
19171        \t\t</div>
19172        \t)
19173        }"
19174        .unindent(),
19175        cx,
19176    )
19177    .await;
19178
19179    assert_indent_guides(
19180        0..8,
19181        vec![
19182            indent_guide(buffer_id, 1, 6, 0),
19183            indent_guide(buffer_id, 2, 5, 1),
19184            indent_guide(buffer_id, 4, 4, 2),
19185        ],
19186        None,
19187        &mut cx,
19188    );
19189}
19190
19191#[gpui::test]
19192async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19193    let (buffer_id, mut cx) = setup_indent_guides_editor(
19194        &"
19195        block1
19196
19197
19198
19199            block2
19200        "
19201        .unindent(),
19202        cx,
19203    )
19204    .await;
19205
19206    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19207}
19208
19209#[gpui::test]
19210async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19211    let (buffer_id, mut cx) = setup_indent_guides_editor(
19212        &"
19213        def a:
19214        \tb = 3
19215        \tif True:
19216        \t\tc = 4
19217        \t\td = 5
19218        \tprint(b)
19219        "
19220        .unindent(),
19221        cx,
19222    )
19223    .await;
19224
19225    assert_indent_guides(
19226        0..6,
19227        vec![
19228            indent_guide(buffer_id, 1, 5, 0),
19229            indent_guide(buffer_id, 3, 4, 1),
19230        ],
19231        None,
19232        &mut cx,
19233    );
19234}
19235
19236#[gpui::test]
19237async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19238    let (buffer_id, mut cx) = setup_indent_guides_editor(
19239        &"
19240    fn main() {
19241        let a = 1;
19242    }"
19243        .unindent(),
19244        cx,
19245    )
19246    .await;
19247
19248    cx.update_editor(|editor, window, cx| {
19249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19250            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19251        });
19252    });
19253
19254    assert_indent_guides(
19255        0..3,
19256        vec![indent_guide(buffer_id, 1, 1, 0)],
19257        Some(vec![0]),
19258        &mut cx,
19259    );
19260}
19261
19262#[gpui::test]
19263async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19264    let (buffer_id, mut cx) = setup_indent_guides_editor(
19265        &"
19266    fn main() {
19267        if 1 == 2 {
19268            let a = 1;
19269        }
19270    }"
19271        .unindent(),
19272        cx,
19273    )
19274    .await;
19275
19276    cx.update_editor(|editor, window, cx| {
19277        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19278            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19279        });
19280    });
19281
19282    assert_indent_guides(
19283        0..4,
19284        vec![
19285            indent_guide(buffer_id, 1, 3, 0),
19286            indent_guide(buffer_id, 2, 2, 1),
19287        ],
19288        Some(vec![1]),
19289        &mut cx,
19290    );
19291
19292    cx.update_editor(|editor, window, cx| {
19293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19294            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19295        });
19296    });
19297
19298    assert_indent_guides(
19299        0..4,
19300        vec![
19301            indent_guide(buffer_id, 1, 3, 0),
19302            indent_guide(buffer_id, 2, 2, 1),
19303        ],
19304        Some(vec![1]),
19305        &mut cx,
19306    );
19307
19308    cx.update_editor(|editor, window, cx| {
19309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19310            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19311        });
19312    });
19313
19314    assert_indent_guides(
19315        0..4,
19316        vec![
19317            indent_guide(buffer_id, 1, 3, 0),
19318            indent_guide(buffer_id, 2, 2, 1),
19319        ],
19320        Some(vec![0]),
19321        &mut cx,
19322    );
19323}
19324
19325#[gpui::test]
19326async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19327    let (buffer_id, mut cx) = setup_indent_guides_editor(
19328        &"
19329    fn main() {
19330        let a = 1;
19331
19332        let b = 2;
19333    }"
19334        .unindent(),
19335        cx,
19336    )
19337    .await;
19338
19339    cx.update_editor(|editor, window, cx| {
19340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19341            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19342        });
19343    });
19344
19345    assert_indent_guides(
19346        0..5,
19347        vec![indent_guide(buffer_id, 1, 3, 0)],
19348        Some(vec![0]),
19349        &mut cx,
19350    );
19351}
19352
19353#[gpui::test]
19354async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19355    let (buffer_id, mut cx) = setup_indent_guides_editor(
19356        &"
19357    def m:
19358        a = 1
19359        pass"
19360            .unindent(),
19361        cx,
19362    )
19363    .await;
19364
19365    cx.update_editor(|editor, window, cx| {
19366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19367            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19368        });
19369    });
19370
19371    assert_indent_guides(
19372        0..3,
19373        vec![indent_guide(buffer_id, 1, 2, 0)],
19374        Some(vec![0]),
19375        &mut cx,
19376    );
19377}
19378
19379#[gpui::test]
19380async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19381    init_test(cx, |_| {});
19382    let mut cx = EditorTestContext::new(cx).await;
19383    let text = indoc! {
19384        "
19385        impl A {
19386            fn b() {
19387                0;
19388                3;
19389                5;
19390                6;
19391                7;
19392            }
19393        }
19394        "
19395    };
19396    let base_text = indoc! {
19397        "
19398        impl A {
19399            fn b() {
19400                0;
19401                1;
19402                2;
19403                3;
19404                4;
19405            }
19406            fn c() {
19407                5;
19408                6;
19409                7;
19410            }
19411        }
19412        "
19413    };
19414
19415    cx.update_editor(|editor, window, cx| {
19416        editor.set_text(text, window, cx);
19417
19418        editor.buffer().update(cx, |multibuffer, cx| {
19419            let buffer = multibuffer.as_singleton().unwrap();
19420            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19421
19422            multibuffer.set_all_diff_hunks_expanded(cx);
19423            multibuffer.add_diff(diff, cx);
19424
19425            buffer.read(cx).remote_id()
19426        })
19427    });
19428    cx.run_until_parked();
19429
19430    cx.assert_state_with_diff(
19431        indoc! { "
19432          impl A {
19433              fn b() {
19434                  0;
19435        -         1;
19436        -         2;
19437                  3;
19438        -         4;
19439        -     }
19440        -     fn c() {
19441                  5;
19442                  6;
19443                  7;
19444              }
19445          }
19446          ˇ"
19447        }
19448        .to_string(),
19449    );
19450
19451    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19452        editor
19453            .snapshot(window, cx)
19454            .buffer_snapshot
19455            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19456            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19457            .collect::<Vec<_>>()
19458    });
19459    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19460    assert_eq!(
19461        actual_guides,
19462        vec![
19463            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19464            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19465            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19466        ]
19467    );
19468}
19469
19470#[gpui::test]
19471async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19472    init_test(cx, |_| {});
19473    let mut cx = EditorTestContext::new(cx).await;
19474
19475    let diff_base = r#"
19476        a
19477        b
19478        c
19479        "#
19480    .unindent();
19481
19482    cx.set_state(
19483        &r#"
19484        ˇA
19485        b
19486        C
19487        "#
19488        .unindent(),
19489    );
19490    cx.set_head_text(&diff_base);
19491    cx.update_editor(|editor, window, cx| {
19492        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19493    });
19494    executor.run_until_parked();
19495
19496    let both_hunks_expanded = r#"
19497        - a
19498        + ˇA
19499          b
19500        - c
19501        + C
19502        "#
19503    .unindent();
19504
19505    cx.assert_state_with_diff(both_hunks_expanded.clone());
19506
19507    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19508        let snapshot = editor.snapshot(window, cx);
19509        let hunks = editor
19510            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19511            .collect::<Vec<_>>();
19512        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19513        let buffer_id = hunks[0].buffer_id;
19514        hunks
19515            .into_iter()
19516            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19517            .collect::<Vec<_>>()
19518    });
19519    assert_eq!(hunk_ranges.len(), 2);
19520
19521    cx.update_editor(|editor, _, cx| {
19522        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19523    });
19524    executor.run_until_parked();
19525
19526    let second_hunk_expanded = r#"
19527          ˇA
19528          b
19529        - c
19530        + C
19531        "#
19532    .unindent();
19533
19534    cx.assert_state_with_diff(second_hunk_expanded);
19535
19536    cx.update_editor(|editor, _, cx| {
19537        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19538    });
19539    executor.run_until_parked();
19540
19541    cx.assert_state_with_diff(both_hunks_expanded.clone());
19542
19543    cx.update_editor(|editor, _, cx| {
19544        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19545    });
19546    executor.run_until_parked();
19547
19548    let first_hunk_expanded = r#"
19549        - a
19550        + ˇA
19551          b
19552          C
19553        "#
19554    .unindent();
19555
19556    cx.assert_state_with_diff(first_hunk_expanded);
19557
19558    cx.update_editor(|editor, _, cx| {
19559        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19560    });
19561    executor.run_until_parked();
19562
19563    cx.assert_state_with_diff(both_hunks_expanded);
19564
19565    cx.set_state(
19566        &r#"
19567        ˇA
19568        b
19569        "#
19570        .unindent(),
19571    );
19572    cx.run_until_parked();
19573
19574    // TODO this cursor position seems bad
19575    cx.assert_state_with_diff(
19576        r#"
19577        - ˇa
19578        + A
19579          b
19580        "#
19581        .unindent(),
19582    );
19583
19584    cx.update_editor(|editor, window, cx| {
19585        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19586    });
19587
19588    cx.assert_state_with_diff(
19589        r#"
19590            - ˇa
19591            + A
19592              b
19593            - c
19594            "#
19595        .unindent(),
19596    );
19597
19598    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19599        let snapshot = editor.snapshot(window, cx);
19600        let hunks = editor
19601            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19602            .collect::<Vec<_>>();
19603        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19604        let buffer_id = hunks[0].buffer_id;
19605        hunks
19606            .into_iter()
19607            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19608            .collect::<Vec<_>>()
19609    });
19610    assert_eq!(hunk_ranges.len(), 2);
19611
19612    cx.update_editor(|editor, _, cx| {
19613        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19614    });
19615    executor.run_until_parked();
19616
19617    cx.assert_state_with_diff(
19618        r#"
19619        - ˇa
19620        + A
19621          b
19622        "#
19623        .unindent(),
19624    );
19625}
19626
19627#[gpui::test]
19628async fn test_toggle_deletion_hunk_at_start_of_file(
19629    executor: BackgroundExecutor,
19630    cx: &mut TestAppContext,
19631) {
19632    init_test(cx, |_| {});
19633    let mut cx = EditorTestContext::new(cx).await;
19634
19635    let diff_base = r#"
19636        a
19637        b
19638        c
19639        "#
19640    .unindent();
19641
19642    cx.set_state(
19643        &r#"
19644        ˇb
19645        c
19646        "#
19647        .unindent(),
19648    );
19649    cx.set_head_text(&diff_base);
19650    cx.update_editor(|editor, window, cx| {
19651        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19652    });
19653    executor.run_until_parked();
19654
19655    let hunk_expanded = r#"
19656        - a
19657          ˇb
19658          c
19659        "#
19660    .unindent();
19661
19662    cx.assert_state_with_diff(hunk_expanded.clone());
19663
19664    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19665        let snapshot = editor.snapshot(window, cx);
19666        let hunks = editor
19667            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19668            .collect::<Vec<_>>();
19669        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19670        let buffer_id = hunks[0].buffer_id;
19671        hunks
19672            .into_iter()
19673            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19674            .collect::<Vec<_>>()
19675    });
19676    assert_eq!(hunk_ranges.len(), 1);
19677
19678    cx.update_editor(|editor, _, cx| {
19679        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19680    });
19681    executor.run_until_parked();
19682
19683    let hunk_collapsed = r#"
19684          ˇb
19685          c
19686        "#
19687    .unindent();
19688
19689    cx.assert_state_with_diff(hunk_collapsed);
19690
19691    cx.update_editor(|editor, _, cx| {
19692        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19693    });
19694    executor.run_until_parked();
19695
19696    cx.assert_state_with_diff(hunk_expanded);
19697}
19698
19699#[gpui::test]
19700async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19701    init_test(cx, |_| {});
19702
19703    let fs = FakeFs::new(cx.executor());
19704    fs.insert_tree(
19705        path!("/test"),
19706        json!({
19707            ".git": {},
19708            "file-1": "ONE\n",
19709            "file-2": "TWO\n",
19710            "file-3": "THREE\n",
19711        }),
19712    )
19713    .await;
19714
19715    fs.set_head_for_repo(
19716        path!("/test/.git").as_ref(),
19717        &[
19718            ("file-1".into(), "one\n".into()),
19719            ("file-2".into(), "two\n".into()),
19720            ("file-3".into(), "three\n".into()),
19721        ],
19722        "deadbeef",
19723    );
19724
19725    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19726    let mut buffers = vec![];
19727    for i in 1..=3 {
19728        let buffer = project
19729            .update(cx, |project, cx| {
19730                let path = format!(path!("/test/file-{}"), i);
19731                project.open_local_buffer(path, cx)
19732            })
19733            .await
19734            .unwrap();
19735        buffers.push(buffer);
19736    }
19737
19738    let multibuffer = cx.new(|cx| {
19739        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19740        multibuffer.set_all_diff_hunks_expanded(cx);
19741        for buffer in &buffers {
19742            let snapshot = buffer.read(cx).snapshot();
19743            multibuffer.set_excerpts_for_path(
19744                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19745                buffer.clone(),
19746                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19747                DEFAULT_MULTIBUFFER_CONTEXT,
19748                cx,
19749            );
19750        }
19751        multibuffer
19752    });
19753
19754    let editor = cx.add_window(|window, cx| {
19755        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19756    });
19757    cx.run_until_parked();
19758
19759    let snapshot = editor
19760        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19761        .unwrap();
19762    let hunks = snapshot
19763        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19764        .map(|hunk| match hunk {
19765            DisplayDiffHunk::Unfolded {
19766                display_row_range, ..
19767            } => display_row_range,
19768            DisplayDiffHunk::Folded { .. } => unreachable!(),
19769        })
19770        .collect::<Vec<_>>();
19771    assert_eq!(
19772        hunks,
19773        [
19774            DisplayRow(2)..DisplayRow(4),
19775            DisplayRow(7)..DisplayRow(9),
19776            DisplayRow(12)..DisplayRow(14),
19777        ]
19778    );
19779}
19780
19781#[gpui::test]
19782async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19783    init_test(cx, |_| {});
19784
19785    let mut cx = EditorTestContext::new(cx).await;
19786    cx.set_head_text(indoc! { "
19787        one
19788        two
19789        three
19790        four
19791        five
19792        "
19793    });
19794    cx.set_index_text(indoc! { "
19795        one
19796        two
19797        three
19798        four
19799        five
19800        "
19801    });
19802    cx.set_state(indoc! {"
19803        one
19804        TWO
19805        ˇTHREE
19806        FOUR
19807        five
19808    "});
19809    cx.run_until_parked();
19810    cx.update_editor(|editor, window, cx| {
19811        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19812    });
19813    cx.run_until_parked();
19814    cx.assert_index_text(Some(indoc! {"
19815        one
19816        TWO
19817        THREE
19818        FOUR
19819        five
19820    "}));
19821    cx.set_state(indoc! { "
19822        one
19823        TWO
19824        ˇTHREE-HUNDRED
19825        FOUR
19826        five
19827    "});
19828    cx.run_until_parked();
19829    cx.update_editor(|editor, window, cx| {
19830        let snapshot = editor.snapshot(window, cx);
19831        let hunks = editor
19832            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19833            .collect::<Vec<_>>();
19834        assert_eq!(hunks.len(), 1);
19835        assert_eq!(
19836            hunks[0].status(),
19837            DiffHunkStatus {
19838                kind: DiffHunkStatusKind::Modified,
19839                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19840            }
19841        );
19842
19843        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19844    });
19845    cx.run_until_parked();
19846    cx.assert_index_text(Some(indoc! {"
19847        one
19848        TWO
19849        THREE-HUNDRED
19850        FOUR
19851        five
19852    "}));
19853}
19854
19855#[gpui::test]
19856fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19857    init_test(cx, |_| {});
19858
19859    let editor = cx.add_window(|window, cx| {
19860        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19861        build_editor(buffer, window, cx)
19862    });
19863
19864    let render_args = Arc::new(Mutex::new(None));
19865    let snapshot = editor
19866        .update(cx, |editor, window, cx| {
19867            let snapshot = editor.buffer().read(cx).snapshot(cx);
19868            let range =
19869                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19870
19871            struct RenderArgs {
19872                row: MultiBufferRow,
19873                folded: bool,
19874                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19875            }
19876
19877            let crease = Crease::inline(
19878                range,
19879                FoldPlaceholder::test(),
19880                {
19881                    let toggle_callback = render_args.clone();
19882                    move |row, folded, callback, _window, _cx| {
19883                        *toggle_callback.lock() = Some(RenderArgs {
19884                            row,
19885                            folded,
19886                            callback,
19887                        });
19888                        div()
19889                    }
19890                },
19891                |_row, _folded, _window, _cx| div(),
19892            );
19893
19894            editor.insert_creases(Some(crease), cx);
19895            let snapshot = editor.snapshot(window, cx);
19896            let _div =
19897                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19898            snapshot
19899        })
19900        .unwrap();
19901
19902    let render_args = render_args.lock().take().unwrap();
19903    assert_eq!(render_args.row, MultiBufferRow(1));
19904    assert!(!render_args.folded);
19905    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19906
19907    cx.update_window(*editor, |_, window, cx| {
19908        (render_args.callback)(true, window, cx)
19909    })
19910    .unwrap();
19911    let snapshot = editor
19912        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19913        .unwrap();
19914    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19915
19916    cx.update_window(*editor, |_, window, cx| {
19917        (render_args.callback)(false, window, cx)
19918    })
19919    .unwrap();
19920    let snapshot = editor
19921        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19922        .unwrap();
19923    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19924}
19925
19926#[gpui::test]
19927async fn test_input_text(cx: &mut TestAppContext) {
19928    init_test(cx, |_| {});
19929    let mut cx = EditorTestContext::new(cx).await;
19930
19931    cx.set_state(
19932        &r#"ˇone
19933        two
19934
19935        three
19936        fourˇ
19937        five
19938
19939        siˇx"#
19940            .unindent(),
19941    );
19942
19943    cx.dispatch_action(HandleInput(String::new()));
19944    cx.assert_editor_state(
19945        &r#"ˇone
19946        two
19947
19948        three
19949        fourˇ
19950        five
19951
19952        siˇx"#
19953            .unindent(),
19954    );
19955
19956    cx.dispatch_action(HandleInput("AAAA".to_string()));
19957    cx.assert_editor_state(
19958        &r#"AAAAˇone
19959        two
19960
19961        three
19962        fourAAAAˇ
19963        five
19964
19965        siAAAAˇx"#
19966            .unindent(),
19967    );
19968}
19969
19970#[gpui::test]
19971async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19972    init_test(cx, |_| {});
19973
19974    let mut cx = EditorTestContext::new(cx).await;
19975    cx.set_state(
19976        r#"let foo = 1;
19977let foo = 2;
19978let foo = 3;
19979let fooˇ = 4;
19980let foo = 5;
19981let foo = 6;
19982let foo = 7;
19983let foo = 8;
19984let foo = 9;
19985let foo = 10;
19986let foo = 11;
19987let foo = 12;
19988let foo = 13;
19989let foo = 14;
19990let foo = 15;"#,
19991    );
19992
19993    cx.update_editor(|e, window, cx| {
19994        assert_eq!(
19995            e.next_scroll_position,
19996            NextScrollCursorCenterTopBottom::Center,
19997            "Default next scroll direction is center",
19998        );
19999
20000        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20001        assert_eq!(
20002            e.next_scroll_position,
20003            NextScrollCursorCenterTopBottom::Top,
20004            "After center, next scroll direction should be top",
20005        );
20006
20007        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20008        assert_eq!(
20009            e.next_scroll_position,
20010            NextScrollCursorCenterTopBottom::Bottom,
20011            "After top, next scroll direction should be bottom",
20012        );
20013
20014        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20015        assert_eq!(
20016            e.next_scroll_position,
20017            NextScrollCursorCenterTopBottom::Center,
20018            "After bottom, scrolling should start over",
20019        );
20020
20021        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20022        assert_eq!(
20023            e.next_scroll_position,
20024            NextScrollCursorCenterTopBottom::Top,
20025            "Scrolling continues if retriggered fast enough"
20026        );
20027    });
20028
20029    cx.executor()
20030        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20031    cx.executor().run_until_parked();
20032    cx.update_editor(|e, _, _| {
20033        assert_eq!(
20034            e.next_scroll_position,
20035            NextScrollCursorCenterTopBottom::Center,
20036            "If scrolling is not triggered fast enough, it should reset"
20037        );
20038    });
20039}
20040
20041#[gpui::test]
20042async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20043    init_test(cx, |_| {});
20044    let mut cx = EditorLspTestContext::new_rust(
20045        lsp::ServerCapabilities {
20046            definition_provider: Some(lsp::OneOf::Left(true)),
20047            references_provider: Some(lsp::OneOf::Left(true)),
20048            ..lsp::ServerCapabilities::default()
20049        },
20050        cx,
20051    )
20052    .await;
20053
20054    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20055        let go_to_definition = cx
20056            .lsp
20057            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20058                move |params, _| async move {
20059                    if empty_go_to_definition {
20060                        Ok(None)
20061                    } else {
20062                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20063                            uri: params.text_document_position_params.text_document.uri,
20064                            range: lsp::Range::new(
20065                                lsp::Position::new(4, 3),
20066                                lsp::Position::new(4, 6),
20067                            ),
20068                        })))
20069                    }
20070                },
20071            );
20072        let references = cx
20073            .lsp
20074            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20075                Ok(Some(vec![lsp::Location {
20076                    uri: params.text_document_position.text_document.uri,
20077                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20078                }]))
20079            });
20080        (go_to_definition, references)
20081    };
20082
20083    cx.set_state(
20084        &r#"fn one() {
20085            let mut a = ˇtwo();
20086        }
20087
20088        fn two() {}"#
20089            .unindent(),
20090    );
20091    set_up_lsp_handlers(false, &mut cx);
20092    let navigated = cx
20093        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20094        .await
20095        .expect("Failed to navigate to definition");
20096    assert_eq!(
20097        navigated,
20098        Navigated::Yes,
20099        "Should have navigated to definition from the GetDefinition response"
20100    );
20101    cx.assert_editor_state(
20102        &r#"fn one() {
20103            let mut a = two();
20104        }
20105
20106        fn «twoˇ»() {}"#
20107            .unindent(),
20108    );
20109
20110    let editors = cx.update_workspace(|workspace, _, cx| {
20111        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20112    });
20113    cx.update_editor(|_, _, test_editor_cx| {
20114        assert_eq!(
20115            editors.len(),
20116            1,
20117            "Initially, only one, test, editor should be open in the workspace"
20118        );
20119        assert_eq!(
20120            test_editor_cx.entity(),
20121            editors.last().expect("Asserted len is 1").clone()
20122        );
20123    });
20124
20125    set_up_lsp_handlers(true, &mut cx);
20126    let navigated = cx
20127        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20128        .await
20129        .expect("Failed to navigate to lookup references");
20130    assert_eq!(
20131        navigated,
20132        Navigated::Yes,
20133        "Should have navigated to references as a fallback after empty GoToDefinition response"
20134    );
20135    // We should not change the selections in the existing file,
20136    // if opening another milti buffer with the references
20137    cx.assert_editor_state(
20138        &r#"fn one() {
20139            let mut a = two();
20140        }
20141
20142        fn «twoˇ»() {}"#
20143            .unindent(),
20144    );
20145    let editors = cx.update_workspace(|workspace, _, cx| {
20146        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20147    });
20148    cx.update_editor(|_, _, test_editor_cx| {
20149        assert_eq!(
20150            editors.len(),
20151            2,
20152            "After falling back to references search, we open a new editor with the results"
20153        );
20154        let references_fallback_text = editors
20155            .into_iter()
20156            .find(|new_editor| *new_editor != test_editor_cx.entity())
20157            .expect("Should have one non-test editor now")
20158            .read(test_editor_cx)
20159            .text(test_editor_cx);
20160        assert_eq!(
20161            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20162            "Should use the range from the references response and not the GoToDefinition one"
20163        );
20164    });
20165}
20166
20167#[gpui::test]
20168async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20169    init_test(cx, |_| {});
20170    cx.update(|cx| {
20171        let mut editor_settings = EditorSettings::get_global(cx).clone();
20172        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20173        EditorSettings::override_global(editor_settings, cx);
20174    });
20175    let mut cx = EditorLspTestContext::new_rust(
20176        lsp::ServerCapabilities {
20177            definition_provider: Some(lsp::OneOf::Left(true)),
20178            references_provider: Some(lsp::OneOf::Left(true)),
20179            ..lsp::ServerCapabilities::default()
20180        },
20181        cx,
20182    )
20183    .await;
20184    let original_state = r#"fn one() {
20185        let mut a = ˇtwo();
20186    }
20187
20188    fn two() {}"#
20189        .unindent();
20190    cx.set_state(&original_state);
20191
20192    let mut go_to_definition = cx
20193        .lsp
20194        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20195            move |_, _| async move { Ok(None) },
20196        );
20197    let _references = cx
20198        .lsp
20199        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20200            panic!("Should not call for references with no go to definition fallback")
20201        });
20202
20203    let navigated = cx
20204        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20205        .await
20206        .expect("Failed to navigate to lookup references");
20207    go_to_definition
20208        .next()
20209        .await
20210        .expect("Should have called the go_to_definition handler");
20211
20212    assert_eq!(
20213        navigated,
20214        Navigated::No,
20215        "Should have navigated to references as a fallback after empty GoToDefinition response"
20216    );
20217    cx.assert_editor_state(&original_state);
20218    let editors = cx.update_workspace(|workspace, _, cx| {
20219        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20220    });
20221    cx.update_editor(|_, _, _| {
20222        assert_eq!(
20223            editors.len(),
20224            1,
20225            "After unsuccessful fallback, no other editor should have been opened"
20226        );
20227    });
20228}
20229
20230#[gpui::test]
20231async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20232    init_test(cx, |_| {});
20233
20234    let language = Arc::new(Language::new(
20235        LanguageConfig::default(),
20236        Some(tree_sitter_rust::LANGUAGE.into()),
20237    ));
20238
20239    let text = r#"
20240        #[cfg(test)]
20241        mod tests() {
20242            #[test]
20243            fn runnable_1() {
20244                let a = 1;
20245            }
20246
20247            #[test]
20248            fn runnable_2() {
20249                let a = 1;
20250                let b = 2;
20251            }
20252        }
20253    "#
20254    .unindent();
20255
20256    let fs = FakeFs::new(cx.executor());
20257    fs.insert_file("/file.rs", Default::default()).await;
20258
20259    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20261    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20262    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20263    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20264
20265    let editor = cx.new_window_entity(|window, cx| {
20266        Editor::new(
20267            EditorMode::full(),
20268            multi_buffer,
20269            Some(project.clone()),
20270            window,
20271            cx,
20272        )
20273    });
20274
20275    editor.update_in(cx, |editor, window, cx| {
20276        let snapshot = editor.buffer().read(cx).snapshot(cx);
20277        editor.tasks.insert(
20278            (buffer.read(cx).remote_id(), 3),
20279            RunnableTasks {
20280                templates: vec![],
20281                offset: snapshot.anchor_before(43),
20282                column: 0,
20283                extra_variables: HashMap::default(),
20284                context_range: BufferOffset(43)..BufferOffset(85),
20285            },
20286        );
20287        editor.tasks.insert(
20288            (buffer.read(cx).remote_id(), 8),
20289            RunnableTasks {
20290                templates: vec![],
20291                offset: snapshot.anchor_before(86),
20292                column: 0,
20293                extra_variables: HashMap::default(),
20294                context_range: BufferOffset(86)..BufferOffset(191),
20295            },
20296        );
20297
20298        // Test finding task when cursor is inside function body
20299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20300            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20301        });
20302        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20303        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20304
20305        // Test finding task when cursor is on function name
20306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20307            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20308        });
20309        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20310        assert_eq!(row, 8, "Should find task when cursor is on function name");
20311    });
20312}
20313
20314#[gpui::test]
20315async fn test_folding_buffers(cx: &mut TestAppContext) {
20316    init_test(cx, |_| {});
20317
20318    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20319    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20320    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20321
20322    let fs = FakeFs::new(cx.executor());
20323    fs.insert_tree(
20324        path!("/a"),
20325        json!({
20326            "first.rs": sample_text_1,
20327            "second.rs": sample_text_2,
20328            "third.rs": sample_text_3,
20329        }),
20330    )
20331    .await;
20332    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20333    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20334    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20335    let worktree = project.update(cx, |project, cx| {
20336        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20337        assert_eq!(worktrees.len(), 1);
20338        worktrees.pop().unwrap()
20339    });
20340    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20341
20342    let buffer_1 = project
20343        .update(cx, |project, cx| {
20344            project.open_buffer((worktree_id, "first.rs"), cx)
20345        })
20346        .await
20347        .unwrap();
20348    let buffer_2 = project
20349        .update(cx, |project, cx| {
20350            project.open_buffer((worktree_id, "second.rs"), cx)
20351        })
20352        .await
20353        .unwrap();
20354    let buffer_3 = project
20355        .update(cx, |project, cx| {
20356            project.open_buffer((worktree_id, "third.rs"), cx)
20357        })
20358        .await
20359        .unwrap();
20360
20361    let multi_buffer = cx.new(|cx| {
20362        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20363        multi_buffer.push_excerpts(
20364            buffer_1.clone(),
20365            [
20366                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20367                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20368                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20369            ],
20370            cx,
20371        );
20372        multi_buffer.push_excerpts(
20373            buffer_2.clone(),
20374            [
20375                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20376                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20377                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20378            ],
20379            cx,
20380        );
20381        multi_buffer.push_excerpts(
20382            buffer_3.clone(),
20383            [
20384                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20385                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20386                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20387            ],
20388            cx,
20389        );
20390        multi_buffer
20391    });
20392    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20393        Editor::new(
20394            EditorMode::full(),
20395            multi_buffer.clone(),
20396            Some(project.clone()),
20397            window,
20398            cx,
20399        )
20400    });
20401
20402    assert_eq!(
20403        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20404        "\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",
20405    );
20406
20407    multi_buffer_editor.update(cx, |editor, cx| {
20408        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20409    });
20410    assert_eq!(
20411        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20412        "\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",
20413        "After folding the first buffer, its text should not be displayed"
20414    );
20415
20416    multi_buffer_editor.update(cx, |editor, cx| {
20417        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20418    });
20419    assert_eq!(
20420        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20421        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20422        "After folding the second buffer, its text should not be displayed"
20423    );
20424
20425    multi_buffer_editor.update(cx, |editor, cx| {
20426        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20427    });
20428    assert_eq!(
20429        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20430        "\n\n\n\n\n",
20431        "After folding the third buffer, its text should not be displayed"
20432    );
20433
20434    // Emulate selection inside the fold logic, that should work
20435    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20436        editor
20437            .snapshot(window, cx)
20438            .next_line_boundary(Point::new(0, 4));
20439    });
20440
20441    multi_buffer_editor.update(cx, |editor, cx| {
20442        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20443    });
20444    assert_eq!(
20445        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20446        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20447        "After unfolding the second buffer, its text should be displayed"
20448    );
20449
20450    // Typing inside of buffer 1 causes that buffer to be unfolded.
20451    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20452        assert_eq!(
20453            multi_buffer
20454                .read(cx)
20455                .snapshot(cx)
20456                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20457                .collect::<String>(),
20458            "bbbb"
20459        );
20460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20461            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20462        });
20463        editor.handle_input("B", window, cx);
20464    });
20465
20466    assert_eq!(
20467        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20468        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20469        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20470    );
20471
20472    multi_buffer_editor.update(cx, |editor, cx| {
20473        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20474    });
20475    assert_eq!(
20476        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20477        "\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",
20478        "After unfolding the all buffers, all original text should be displayed"
20479    );
20480}
20481
20482#[gpui::test]
20483async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20484    init_test(cx, |_| {});
20485
20486    let sample_text_1 = "1111\n2222\n3333".to_string();
20487    let sample_text_2 = "4444\n5555\n6666".to_string();
20488    let sample_text_3 = "7777\n8888\n9999".to_string();
20489
20490    let fs = FakeFs::new(cx.executor());
20491    fs.insert_tree(
20492        path!("/a"),
20493        json!({
20494            "first.rs": sample_text_1,
20495            "second.rs": sample_text_2,
20496            "third.rs": sample_text_3,
20497        }),
20498    )
20499    .await;
20500    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20501    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20502    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20503    let worktree = project.update(cx, |project, cx| {
20504        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20505        assert_eq!(worktrees.len(), 1);
20506        worktrees.pop().unwrap()
20507    });
20508    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20509
20510    let buffer_1 = project
20511        .update(cx, |project, cx| {
20512            project.open_buffer((worktree_id, "first.rs"), cx)
20513        })
20514        .await
20515        .unwrap();
20516    let buffer_2 = project
20517        .update(cx, |project, cx| {
20518            project.open_buffer((worktree_id, "second.rs"), cx)
20519        })
20520        .await
20521        .unwrap();
20522    let buffer_3 = project
20523        .update(cx, |project, cx| {
20524            project.open_buffer((worktree_id, "third.rs"), cx)
20525        })
20526        .await
20527        .unwrap();
20528
20529    let multi_buffer = cx.new(|cx| {
20530        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20531        multi_buffer.push_excerpts(
20532            buffer_1.clone(),
20533            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20534            cx,
20535        );
20536        multi_buffer.push_excerpts(
20537            buffer_2.clone(),
20538            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20539            cx,
20540        );
20541        multi_buffer.push_excerpts(
20542            buffer_3.clone(),
20543            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20544            cx,
20545        );
20546        multi_buffer
20547    });
20548
20549    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20550        Editor::new(
20551            EditorMode::full(),
20552            multi_buffer,
20553            Some(project.clone()),
20554            window,
20555            cx,
20556        )
20557    });
20558
20559    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20560    assert_eq!(
20561        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20562        full_text,
20563    );
20564
20565    multi_buffer_editor.update(cx, |editor, cx| {
20566        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20567    });
20568    assert_eq!(
20569        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20570        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20571        "After folding the first buffer, its text should not be displayed"
20572    );
20573
20574    multi_buffer_editor.update(cx, |editor, cx| {
20575        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20576    });
20577
20578    assert_eq!(
20579        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20580        "\n\n\n\n\n\n7777\n8888\n9999",
20581        "After folding the second buffer, its text should not be displayed"
20582    );
20583
20584    multi_buffer_editor.update(cx, |editor, cx| {
20585        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20586    });
20587    assert_eq!(
20588        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20589        "\n\n\n\n\n",
20590        "After folding the third buffer, its text should not be displayed"
20591    );
20592
20593    multi_buffer_editor.update(cx, |editor, cx| {
20594        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20595    });
20596    assert_eq!(
20597        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20598        "\n\n\n\n4444\n5555\n6666\n\n",
20599        "After unfolding the second buffer, its text should be displayed"
20600    );
20601
20602    multi_buffer_editor.update(cx, |editor, cx| {
20603        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20604    });
20605    assert_eq!(
20606        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20607        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20608        "After unfolding the first buffer, its text should be displayed"
20609    );
20610
20611    multi_buffer_editor.update(cx, |editor, cx| {
20612        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20613    });
20614    assert_eq!(
20615        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20616        full_text,
20617        "After unfolding all buffers, all original text should be displayed"
20618    );
20619}
20620
20621#[gpui::test]
20622async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20623    init_test(cx, |_| {});
20624
20625    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20626
20627    let fs = FakeFs::new(cx.executor());
20628    fs.insert_tree(
20629        path!("/a"),
20630        json!({
20631            "main.rs": sample_text,
20632        }),
20633    )
20634    .await;
20635    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20636    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20637    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20638    let worktree = project.update(cx, |project, cx| {
20639        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20640        assert_eq!(worktrees.len(), 1);
20641        worktrees.pop().unwrap()
20642    });
20643    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20644
20645    let buffer_1 = project
20646        .update(cx, |project, cx| {
20647            project.open_buffer((worktree_id, "main.rs"), cx)
20648        })
20649        .await
20650        .unwrap();
20651
20652    let multi_buffer = cx.new(|cx| {
20653        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20654        multi_buffer.push_excerpts(
20655            buffer_1.clone(),
20656            [ExcerptRange::new(
20657                Point::new(0, 0)
20658                    ..Point::new(
20659                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20660                        0,
20661                    ),
20662            )],
20663            cx,
20664        );
20665        multi_buffer
20666    });
20667    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20668        Editor::new(
20669            EditorMode::full(),
20670            multi_buffer,
20671            Some(project.clone()),
20672            window,
20673            cx,
20674        )
20675    });
20676
20677    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20678    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20679        enum TestHighlight {}
20680        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20681        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20682        editor.highlight_text::<TestHighlight>(
20683            vec![highlight_range.clone()],
20684            HighlightStyle::color(Hsla::green()),
20685            cx,
20686        );
20687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20688            s.select_ranges(Some(highlight_range))
20689        });
20690    });
20691
20692    let full_text = format!("\n\n{sample_text}");
20693    assert_eq!(
20694        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20695        full_text,
20696    );
20697}
20698
20699#[gpui::test]
20700async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20701    init_test(cx, |_| {});
20702    cx.update(|cx| {
20703        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20704            "keymaps/default-linux.json",
20705            cx,
20706        )
20707        .unwrap();
20708        cx.bind_keys(default_key_bindings);
20709    });
20710
20711    let (editor, cx) = cx.add_window_view(|window, cx| {
20712        let multi_buffer = MultiBuffer::build_multi(
20713            [
20714                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20715                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20716                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20717                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20718            ],
20719            cx,
20720        );
20721        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20722
20723        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20724        // fold all but the second buffer, so that we test navigating between two
20725        // adjacent folded buffers, as well as folded buffers at the start and
20726        // end the multibuffer
20727        editor.fold_buffer(buffer_ids[0], cx);
20728        editor.fold_buffer(buffer_ids[2], cx);
20729        editor.fold_buffer(buffer_ids[3], cx);
20730
20731        editor
20732    });
20733    cx.simulate_resize(size(px(1000.), px(1000.)));
20734
20735    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20736    cx.assert_excerpts_with_selections(indoc! {"
20737        [EXCERPT]
20738        ˇ[FOLDED]
20739        [EXCERPT]
20740        a1
20741        b1
20742        [EXCERPT]
20743        [FOLDED]
20744        [EXCERPT]
20745        [FOLDED]
20746        "
20747    });
20748    cx.simulate_keystroke("down");
20749    cx.assert_excerpts_with_selections(indoc! {"
20750        [EXCERPT]
20751        [FOLDED]
20752        [EXCERPT]
20753        ˇa1
20754        b1
20755        [EXCERPT]
20756        [FOLDED]
20757        [EXCERPT]
20758        [FOLDED]
20759        "
20760    });
20761    cx.simulate_keystroke("down");
20762    cx.assert_excerpts_with_selections(indoc! {"
20763        [EXCERPT]
20764        [FOLDED]
20765        [EXCERPT]
20766        a1
20767        ˇb1
20768        [EXCERPT]
20769        [FOLDED]
20770        [EXCERPT]
20771        [FOLDED]
20772        "
20773    });
20774    cx.simulate_keystroke("down");
20775    cx.assert_excerpts_with_selections(indoc! {"
20776        [EXCERPT]
20777        [FOLDED]
20778        [EXCERPT]
20779        a1
20780        b1
20781        ˇ[EXCERPT]
20782        [FOLDED]
20783        [EXCERPT]
20784        [FOLDED]
20785        "
20786    });
20787    cx.simulate_keystroke("down");
20788    cx.assert_excerpts_with_selections(indoc! {"
20789        [EXCERPT]
20790        [FOLDED]
20791        [EXCERPT]
20792        a1
20793        b1
20794        [EXCERPT]
20795        ˇ[FOLDED]
20796        [EXCERPT]
20797        [FOLDED]
20798        "
20799    });
20800    for _ in 0..5 {
20801        cx.simulate_keystroke("down");
20802        cx.assert_excerpts_with_selections(indoc! {"
20803            [EXCERPT]
20804            [FOLDED]
20805            [EXCERPT]
20806            a1
20807            b1
20808            [EXCERPT]
20809            [FOLDED]
20810            [EXCERPT]
20811            ˇ[FOLDED]
20812            "
20813        });
20814    }
20815
20816    cx.simulate_keystroke("up");
20817    cx.assert_excerpts_with_selections(indoc! {"
20818        [EXCERPT]
20819        [FOLDED]
20820        [EXCERPT]
20821        a1
20822        b1
20823        [EXCERPT]
20824        ˇ[FOLDED]
20825        [EXCERPT]
20826        [FOLDED]
20827        "
20828    });
20829    cx.simulate_keystroke("up");
20830    cx.assert_excerpts_with_selections(indoc! {"
20831        [EXCERPT]
20832        [FOLDED]
20833        [EXCERPT]
20834        a1
20835        b1
20836        ˇ[EXCERPT]
20837        [FOLDED]
20838        [EXCERPT]
20839        [FOLDED]
20840        "
20841    });
20842    cx.simulate_keystroke("up");
20843    cx.assert_excerpts_with_selections(indoc! {"
20844        [EXCERPT]
20845        [FOLDED]
20846        [EXCERPT]
20847        a1
20848        ˇb1
20849        [EXCERPT]
20850        [FOLDED]
20851        [EXCERPT]
20852        [FOLDED]
20853        "
20854    });
20855    cx.simulate_keystroke("up");
20856    cx.assert_excerpts_with_selections(indoc! {"
20857        [EXCERPT]
20858        [FOLDED]
20859        [EXCERPT]
20860        ˇa1
20861        b1
20862        [EXCERPT]
20863        [FOLDED]
20864        [EXCERPT]
20865        [FOLDED]
20866        "
20867    });
20868    for _ in 0..5 {
20869        cx.simulate_keystroke("up");
20870        cx.assert_excerpts_with_selections(indoc! {"
20871            [EXCERPT]
20872            ˇ[FOLDED]
20873            [EXCERPT]
20874            a1
20875            b1
20876            [EXCERPT]
20877            [FOLDED]
20878            [EXCERPT]
20879            [FOLDED]
20880            "
20881        });
20882    }
20883}
20884
20885#[gpui::test]
20886async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20887    init_test(cx, |_| {});
20888
20889    // Simple insertion
20890    assert_highlighted_edits(
20891        "Hello, world!",
20892        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20893        true,
20894        cx,
20895        |highlighted_edits, cx| {
20896            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20897            assert_eq!(highlighted_edits.highlights.len(), 1);
20898            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20899            assert_eq!(
20900                highlighted_edits.highlights[0].1.background_color,
20901                Some(cx.theme().status().created_background)
20902            );
20903        },
20904    )
20905    .await;
20906
20907    // Replacement
20908    assert_highlighted_edits(
20909        "This is a test.",
20910        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20911        false,
20912        cx,
20913        |highlighted_edits, cx| {
20914            assert_eq!(highlighted_edits.text, "That is a test.");
20915            assert_eq!(highlighted_edits.highlights.len(), 1);
20916            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20917            assert_eq!(
20918                highlighted_edits.highlights[0].1.background_color,
20919                Some(cx.theme().status().created_background)
20920            );
20921        },
20922    )
20923    .await;
20924
20925    // Multiple edits
20926    assert_highlighted_edits(
20927        "Hello, world!",
20928        vec![
20929            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20930            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20931        ],
20932        false,
20933        cx,
20934        |highlighted_edits, cx| {
20935            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20936            assert_eq!(highlighted_edits.highlights.len(), 2);
20937            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20938            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20939            assert_eq!(
20940                highlighted_edits.highlights[0].1.background_color,
20941                Some(cx.theme().status().created_background)
20942            );
20943            assert_eq!(
20944                highlighted_edits.highlights[1].1.background_color,
20945                Some(cx.theme().status().created_background)
20946            );
20947        },
20948    )
20949    .await;
20950
20951    // Multiple lines with edits
20952    assert_highlighted_edits(
20953        "First line\nSecond line\nThird line\nFourth line",
20954        vec![
20955            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20956            (
20957                Point::new(2, 0)..Point::new(2, 10),
20958                "New third line".to_string(),
20959            ),
20960            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20961        ],
20962        false,
20963        cx,
20964        |highlighted_edits, cx| {
20965            assert_eq!(
20966                highlighted_edits.text,
20967                "Second modified\nNew third line\nFourth updated line"
20968            );
20969            assert_eq!(highlighted_edits.highlights.len(), 3);
20970            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20971            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20972            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20973            for highlight in &highlighted_edits.highlights {
20974                assert_eq!(
20975                    highlight.1.background_color,
20976                    Some(cx.theme().status().created_background)
20977                );
20978            }
20979        },
20980    )
20981    .await;
20982}
20983
20984#[gpui::test]
20985async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20986    init_test(cx, |_| {});
20987
20988    // Deletion
20989    assert_highlighted_edits(
20990        "Hello, world!",
20991        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20992        true,
20993        cx,
20994        |highlighted_edits, cx| {
20995            assert_eq!(highlighted_edits.text, "Hello, world!");
20996            assert_eq!(highlighted_edits.highlights.len(), 1);
20997            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20998            assert_eq!(
20999                highlighted_edits.highlights[0].1.background_color,
21000                Some(cx.theme().status().deleted_background)
21001            );
21002        },
21003    )
21004    .await;
21005
21006    // Insertion
21007    assert_highlighted_edits(
21008        "Hello, world!",
21009        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21010        true,
21011        cx,
21012        |highlighted_edits, cx| {
21013            assert_eq!(highlighted_edits.highlights.len(), 1);
21014            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21015            assert_eq!(
21016                highlighted_edits.highlights[0].1.background_color,
21017                Some(cx.theme().status().created_background)
21018            );
21019        },
21020    )
21021    .await;
21022}
21023
21024async fn assert_highlighted_edits(
21025    text: &str,
21026    edits: Vec<(Range<Point>, String)>,
21027    include_deletions: bool,
21028    cx: &mut TestAppContext,
21029    assertion_fn: impl Fn(HighlightedText, &App),
21030) {
21031    let window = cx.add_window(|window, cx| {
21032        let buffer = MultiBuffer::build_simple(text, cx);
21033        Editor::new(EditorMode::full(), buffer, None, window, cx)
21034    });
21035    let cx = &mut VisualTestContext::from_window(*window, cx);
21036
21037    let (buffer, snapshot) = window
21038        .update(cx, |editor, _window, cx| {
21039            (
21040                editor.buffer().clone(),
21041                editor.buffer().read(cx).snapshot(cx),
21042            )
21043        })
21044        .unwrap();
21045
21046    let edits = edits
21047        .into_iter()
21048        .map(|(range, edit)| {
21049            (
21050                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21051                edit,
21052            )
21053        })
21054        .collect::<Vec<_>>();
21055
21056    let text_anchor_edits = edits
21057        .clone()
21058        .into_iter()
21059        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21060        .collect::<Vec<_>>();
21061
21062    let edit_preview = window
21063        .update(cx, |_, _window, cx| {
21064            buffer
21065                .read(cx)
21066                .as_singleton()
21067                .unwrap()
21068                .read(cx)
21069                .preview_edits(text_anchor_edits.into(), cx)
21070        })
21071        .unwrap()
21072        .await;
21073
21074    cx.update(|_window, cx| {
21075        let highlighted_edits = edit_prediction_edit_text(
21076            snapshot.as_singleton().unwrap().2,
21077            &edits,
21078            &edit_preview,
21079            include_deletions,
21080            cx,
21081        );
21082        assertion_fn(highlighted_edits, cx)
21083    });
21084}
21085
21086#[track_caller]
21087fn assert_breakpoint(
21088    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21089    path: &Arc<Path>,
21090    expected: Vec<(u32, Breakpoint)>,
21091) {
21092    if expected.is_empty() {
21093        assert!(!breakpoints.contains_key(path), "{}", path.display());
21094    } else {
21095        let mut breakpoint = breakpoints
21096            .get(path)
21097            .unwrap()
21098            .iter()
21099            .map(|breakpoint| {
21100                (
21101                    breakpoint.row,
21102                    Breakpoint {
21103                        message: breakpoint.message.clone(),
21104                        state: breakpoint.state,
21105                        condition: breakpoint.condition.clone(),
21106                        hit_condition: breakpoint.hit_condition.clone(),
21107                    },
21108                )
21109            })
21110            .collect::<Vec<_>>();
21111
21112        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21113
21114        assert_eq!(expected, breakpoint);
21115    }
21116}
21117
21118fn add_log_breakpoint_at_cursor(
21119    editor: &mut Editor,
21120    log_message: &str,
21121    window: &mut Window,
21122    cx: &mut Context<Editor>,
21123) {
21124    let (anchor, bp) = editor
21125        .breakpoints_at_cursors(window, cx)
21126        .first()
21127        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21128        .unwrap_or_else(|| {
21129            let cursor_position: Point = editor.selections.newest(cx).head();
21130
21131            let breakpoint_position = editor
21132                .snapshot(window, cx)
21133                .display_snapshot
21134                .buffer_snapshot
21135                .anchor_before(Point::new(cursor_position.row, 0));
21136
21137            (breakpoint_position, Breakpoint::new_log(log_message))
21138        });
21139
21140    editor.edit_breakpoint_at_anchor(
21141        anchor,
21142        bp,
21143        BreakpointEditAction::EditLogMessage(log_message.into()),
21144        cx,
21145    );
21146}
21147
21148#[gpui::test]
21149async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21150    init_test(cx, |_| {});
21151
21152    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21153    let fs = FakeFs::new(cx.executor());
21154    fs.insert_tree(
21155        path!("/a"),
21156        json!({
21157            "main.rs": sample_text,
21158        }),
21159    )
21160    .await;
21161    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21162    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21163    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21164
21165    let fs = FakeFs::new(cx.executor());
21166    fs.insert_tree(
21167        path!("/a"),
21168        json!({
21169            "main.rs": sample_text,
21170        }),
21171    )
21172    .await;
21173    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21174    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21175    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21176    let worktree_id = workspace
21177        .update(cx, |workspace, _window, cx| {
21178            workspace.project().update(cx, |project, cx| {
21179                project.worktrees(cx).next().unwrap().read(cx).id()
21180            })
21181        })
21182        .unwrap();
21183
21184    let buffer = project
21185        .update(cx, |project, cx| {
21186            project.open_buffer((worktree_id, "main.rs"), cx)
21187        })
21188        .await
21189        .unwrap();
21190
21191    let (editor, cx) = cx.add_window_view(|window, cx| {
21192        Editor::new(
21193            EditorMode::full(),
21194            MultiBuffer::build_from_buffer(buffer, cx),
21195            Some(project.clone()),
21196            window,
21197            cx,
21198        )
21199    });
21200
21201    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21202    let abs_path = project.read_with(cx, |project, cx| {
21203        project
21204            .absolute_path(&project_path, cx)
21205            .map(Arc::from)
21206            .unwrap()
21207    });
21208
21209    // assert we can add breakpoint on the first line
21210    editor.update_in(cx, |editor, window, cx| {
21211        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21212        editor.move_to_end(&MoveToEnd, window, cx);
21213        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21214    });
21215
21216    let breakpoints = editor.update(cx, |editor, cx| {
21217        editor
21218            .breakpoint_store()
21219            .as_ref()
21220            .unwrap()
21221            .read(cx)
21222            .all_source_breakpoints(cx)
21223    });
21224
21225    assert_eq!(1, breakpoints.len());
21226    assert_breakpoint(
21227        &breakpoints,
21228        &abs_path,
21229        vec![
21230            (0, Breakpoint::new_standard()),
21231            (3, Breakpoint::new_standard()),
21232        ],
21233    );
21234
21235    editor.update_in(cx, |editor, window, cx| {
21236        editor.move_to_beginning(&MoveToBeginning, window, cx);
21237        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21238    });
21239
21240    let breakpoints = editor.update(cx, |editor, cx| {
21241        editor
21242            .breakpoint_store()
21243            .as_ref()
21244            .unwrap()
21245            .read(cx)
21246            .all_source_breakpoints(cx)
21247    });
21248
21249    assert_eq!(1, breakpoints.len());
21250    assert_breakpoint(
21251        &breakpoints,
21252        &abs_path,
21253        vec![(3, Breakpoint::new_standard())],
21254    );
21255
21256    editor.update_in(cx, |editor, window, cx| {
21257        editor.move_to_end(&MoveToEnd, window, cx);
21258        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21259    });
21260
21261    let breakpoints = editor.update(cx, |editor, cx| {
21262        editor
21263            .breakpoint_store()
21264            .as_ref()
21265            .unwrap()
21266            .read(cx)
21267            .all_source_breakpoints(cx)
21268    });
21269
21270    assert_eq!(0, breakpoints.len());
21271    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21272}
21273
21274#[gpui::test]
21275async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277
21278    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21279
21280    let fs = FakeFs::new(cx.executor());
21281    fs.insert_tree(
21282        path!("/a"),
21283        json!({
21284            "main.rs": sample_text,
21285        }),
21286    )
21287    .await;
21288    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21289    let (workspace, cx) =
21290        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21291
21292    let worktree_id = workspace.update(cx, |workspace, cx| {
21293        workspace.project().update(cx, |project, cx| {
21294            project.worktrees(cx).next().unwrap().read(cx).id()
21295        })
21296    });
21297
21298    let buffer = project
21299        .update(cx, |project, cx| {
21300            project.open_buffer((worktree_id, "main.rs"), cx)
21301        })
21302        .await
21303        .unwrap();
21304
21305    let (editor, cx) = cx.add_window_view(|window, cx| {
21306        Editor::new(
21307            EditorMode::full(),
21308            MultiBuffer::build_from_buffer(buffer, cx),
21309            Some(project.clone()),
21310            window,
21311            cx,
21312        )
21313    });
21314
21315    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21316    let abs_path = project.read_with(cx, |project, cx| {
21317        project
21318            .absolute_path(&project_path, cx)
21319            .map(Arc::from)
21320            .unwrap()
21321    });
21322
21323    editor.update_in(cx, |editor, window, cx| {
21324        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21325    });
21326
21327    let breakpoints = editor.update(cx, |editor, cx| {
21328        editor
21329            .breakpoint_store()
21330            .as_ref()
21331            .unwrap()
21332            .read(cx)
21333            .all_source_breakpoints(cx)
21334    });
21335
21336    assert_breakpoint(
21337        &breakpoints,
21338        &abs_path,
21339        vec![(0, Breakpoint::new_log("hello world"))],
21340    );
21341
21342    // Removing a log message from a log breakpoint should remove it
21343    editor.update_in(cx, |editor, window, cx| {
21344        add_log_breakpoint_at_cursor(editor, "", window, cx);
21345    });
21346
21347    let breakpoints = editor.update(cx, |editor, cx| {
21348        editor
21349            .breakpoint_store()
21350            .as_ref()
21351            .unwrap()
21352            .read(cx)
21353            .all_source_breakpoints(cx)
21354    });
21355
21356    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21357
21358    editor.update_in(cx, |editor, window, cx| {
21359        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21360        editor.move_to_end(&MoveToEnd, window, cx);
21361        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21362        // Not adding a log message to a standard breakpoint shouldn't remove it
21363        add_log_breakpoint_at_cursor(editor, "", window, cx);
21364    });
21365
21366    let breakpoints = editor.update(cx, |editor, cx| {
21367        editor
21368            .breakpoint_store()
21369            .as_ref()
21370            .unwrap()
21371            .read(cx)
21372            .all_source_breakpoints(cx)
21373    });
21374
21375    assert_breakpoint(
21376        &breakpoints,
21377        &abs_path,
21378        vec![
21379            (0, Breakpoint::new_standard()),
21380            (3, Breakpoint::new_standard()),
21381        ],
21382    );
21383
21384    editor.update_in(cx, |editor, window, cx| {
21385        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21386    });
21387
21388    let breakpoints = editor.update(cx, |editor, cx| {
21389        editor
21390            .breakpoint_store()
21391            .as_ref()
21392            .unwrap()
21393            .read(cx)
21394            .all_source_breakpoints(cx)
21395    });
21396
21397    assert_breakpoint(
21398        &breakpoints,
21399        &abs_path,
21400        vec![
21401            (0, Breakpoint::new_standard()),
21402            (3, Breakpoint::new_log("hello world")),
21403        ],
21404    );
21405
21406    editor.update_in(cx, |editor, window, cx| {
21407        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21408    });
21409
21410    let breakpoints = editor.update(cx, |editor, cx| {
21411        editor
21412            .breakpoint_store()
21413            .as_ref()
21414            .unwrap()
21415            .read(cx)
21416            .all_source_breakpoints(cx)
21417    });
21418
21419    assert_breakpoint(
21420        &breakpoints,
21421        &abs_path,
21422        vec![
21423            (0, Breakpoint::new_standard()),
21424            (3, Breakpoint::new_log("hello Earth!!")),
21425        ],
21426    );
21427}
21428
21429/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21430/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21431/// or when breakpoints were placed out of order. This tests for a regression too
21432#[gpui::test]
21433async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21434    init_test(cx, |_| {});
21435
21436    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21437    let fs = FakeFs::new(cx.executor());
21438    fs.insert_tree(
21439        path!("/a"),
21440        json!({
21441            "main.rs": sample_text,
21442        }),
21443    )
21444    .await;
21445    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21446    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21447    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21448
21449    let fs = FakeFs::new(cx.executor());
21450    fs.insert_tree(
21451        path!("/a"),
21452        json!({
21453            "main.rs": sample_text,
21454        }),
21455    )
21456    .await;
21457    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21458    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21459    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21460    let worktree_id = workspace
21461        .update(cx, |workspace, _window, cx| {
21462            workspace.project().update(cx, |project, cx| {
21463                project.worktrees(cx).next().unwrap().read(cx).id()
21464            })
21465        })
21466        .unwrap();
21467
21468    let buffer = project
21469        .update(cx, |project, cx| {
21470            project.open_buffer((worktree_id, "main.rs"), cx)
21471        })
21472        .await
21473        .unwrap();
21474
21475    let (editor, cx) = cx.add_window_view(|window, cx| {
21476        Editor::new(
21477            EditorMode::full(),
21478            MultiBuffer::build_from_buffer(buffer, cx),
21479            Some(project.clone()),
21480            window,
21481            cx,
21482        )
21483    });
21484
21485    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21486    let abs_path = project.read_with(cx, |project, cx| {
21487        project
21488            .absolute_path(&project_path, cx)
21489            .map(Arc::from)
21490            .unwrap()
21491    });
21492
21493    // assert we can add breakpoint on the first line
21494    editor.update_in(cx, |editor, window, cx| {
21495        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21496        editor.move_to_end(&MoveToEnd, window, cx);
21497        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21498        editor.move_up(&MoveUp, window, cx);
21499        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21500    });
21501
21502    let breakpoints = editor.update(cx, |editor, cx| {
21503        editor
21504            .breakpoint_store()
21505            .as_ref()
21506            .unwrap()
21507            .read(cx)
21508            .all_source_breakpoints(cx)
21509    });
21510
21511    assert_eq!(1, breakpoints.len());
21512    assert_breakpoint(
21513        &breakpoints,
21514        &abs_path,
21515        vec![
21516            (0, Breakpoint::new_standard()),
21517            (2, Breakpoint::new_standard()),
21518            (3, Breakpoint::new_standard()),
21519        ],
21520    );
21521
21522    editor.update_in(cx, |editor, window, cx| {
21523        editor.move_to_beginning(&MoveToBeginning, window, cx);
21524        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21525        editor.move_to_end(&MoveToEnd, window, cx);
21526        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21527        // Disabling a breakpoint that doesn't exist should do nothing
21528        editor.move_up(&MoveUp, window, cx);
21529        editor.move_up(&MoveUp, window, cx);
21530        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21531    });
21532
21533    let breakpoints = editor.update(cx, |editor, cx| {
21534        editor
21535            .breakpoint_store()
21536            .as_ref()
21537            .unwrap()
21538            .read(cx)
21539            .all_source_breakpoints(cx)
21540    });
21541
21542    let disable_breakpoint = {
21543        let mut bp = Breakpoint::new_standard();
21544        bp.state = BreakpointState::Disabled;
21545        bp
21546    };
21547
21548    assert_eq!(1, breakpoints.len());
21549    assert_breakpoint(
21550        &breakpoints,
21551        &abs_path,
21552        vec![
21553            (0, disable_breakpoint.clone()),
21554            (2, Breakpoint::new_standard()),
21555            (3, disable_breakpoint.clone()),
21556        ],
21557    );
21558
21559    editor.update_in(cx, |editor, window, cx| {
21560        editor.move_to_beginning(&MoveToBeginning, window, cx);
21561        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21562        editor.move_to_end(&MoveToEnd, window, cx);
21563        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21564        editor.move_up(&MoveUp, window, cx);
21565        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21566    });
21567
21568    let breakpoints = editor.update(cx, |editor, cx| {
21569        editor
21570            .breakpoint_store()
21571            .as_ref()
21572            .unwrap()
21573            .read(cx)
21574            .all_source_breakpoints(cx)
21575    });
21576
21577    assert_eq!(1, breakpoints.len());
21578    assert_breakpoint(
21579        &breakpoints,
21580        &abs_path,
21581        vec![
21582            (0, Breakpoint::new_standard()),
21583            (2, disable_breakpoint),
21584            (3, Breakpoint::new_standard()),
21585        ],
21586    );
21587}
21588
21589#[gpui::test]
21590async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21591    init_test(cx, |_| {});
21592    let capabilities = lsp::ServerCapabilities {
21593        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21594            prepare_provider: Some(true),
21595            work_done_progress_options: Default::default(),
21596        })),
21597        ..Default::default()
21598    };
21599    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21600
21601    cx.set_state(indoc! {"
21602        struct Fˇoo {}
21603    "});
21604
21605    cx.update_editor(|editor, _, cx| {
21606        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21607        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21608        editor.highlight_background::<DocumentHighlightRead>(
21609            &[highlight_range],
21610            |theme| theme.colors().editor_document_highlight_read_background,
21611            cx,
21612        );
21613    });
21614
21615    let mut prepare_rename_handler = cx
21616        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21617            move |_, _, _| async move {
21618                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21619                    start: lsp::Position {
21620                        line: 0,
21621                        character: 7,
21622                    },
21623                    end: lsp::Position {
21624                        line: 0,
21625                        character: 10,
21626                    },
21627                })))
21628            },
21629        );
21630    let prepare_rename_task = cx
21631        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21632        .expect("Prepare rename was not started");
21633    prepare_rename_handler.next().await.unwrap();
21634    prepare_rename_task.await.expect("Prepare rename failed");
21635
21636    let mut rename_handler =
21637        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21638            let edit = lsp::TextEdit {
21639                range: lsp::Range {
21640                    start: lsp::Position {
21641                        line: 0,
21642                        character: 7,
21643                    },
21644                    end: lsp::Position {
21645                        line: 0,
21646                        character: 10,
21647                    },
21648                },
21649                new_text: "FooRenamed".to_string(),
21650            };
21651            Ok(Some(lsp::WorkspaceEdit::new(
21652                // Specify the same edit twice
21653                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21654            )))
21655        });
21656    let rename_task = cx
21657        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21658        .expect("Confirm rename was not started");
21659    rename_handler.next().await.unwrap();
21660    rename_task.await.expect("Confirm rename failed");
21661    cx.run_until_parked();
21662
21663    // Despite two edits, only one is actually applied as those are identical
21664    cx.assert_editor_state(indoc! {"
21665        struct FooRenamedˇ {}
21666    "});
21667}
21668
21669#[gpui::test]
21670async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21671    init_test(cx, |_| {});
21672    // These capabilities indicate that the server does not support prepare rename.
21673    let capabilities = lsp::ServerCapabilities {
21674        rename_provider: Some(lsp::OneOf::Left(true)),
21675        ..Default::default()
21676    };
21677    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21678
21679    cx.set_state(indoc! {"
21680        struct Fˇoo {}
21681    "});
21682
21683    cx.update_editor(|editor, _window, cx| {
21684        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21685        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21686        editor.highlight_background::<DocumentHighlightRead>(
21687            &[highlight_range],
21688            |theme| theme.colors().editor_document_highlight_read_background,
21689            cx,
21690        );
21691    });
21692
21693    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21694        .expect("Prepare rename was not started")
21695        .await
21696        .expect("Prepare rename failed");
21697
21698    let mut rename_handler =
21699        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21700            let edit = lsp::TextEdit {
21701                range: lsp::Range {
21702                    start: lsp::Position {
21703                        line: 0,
21704                        character: 7,
21705                    },
21706                    end: lsp::Position {
21707                        line: 0,
21708                        character: 10,
21709                    },
21710                },
21711                new_text: "FooRenamed".to_string(),
21712            };
21713            Ok(Some(lsp::WorkspaceEdit::new(
21714                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21715            )))
21716        });
21717    let rename_task = cx
21718        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21719        .expect("Confirm rename was not started");
21720    rename_handler.next().await.unwrap();
21721    rename_task.await.expect("Confirm rename failed");
21722    cx.run_until_parked();
21723
21724    // Correct range is renamed, as `surrounding_word` is used to find it.
21725    cx.assert_editor_state(indoc! {"
21726        struct FooRenamedˇ {}
21727    "});
21728}
21729
21730#[gpui::test]
21731async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21732    init_test(cx, |_| {});
21733    let mut cx = EditorTestContext::new(cx).await;
21734
21735    let language = Arc::new(
21736        Language::new(
21737            LanguageConfig::default(),
21738            Some(tree_sitter_html::LANGUAGE.into()),
21739        )
21740        .with_brackets_query(
21741            r#"
21742            ("<" @open "/>" @close)
21743            ("</" @open ">" @close)
21744            ("<" @open ">" @close)
21745            ("\"" @open "\"" @close)
21746            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21747        "#,
21748        )
21749        .unwrap(),
21750    );
21751    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21752
21753    cx.set_state(indoc! {"
21754        <span>ˇ</span>
21755    "});
21756    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21757    cx.assert_editor_state(indoc! {"
21758        <span>
21759        ˇ
21760        </span>
21761    "});
21762
21763    cx.set_state(indoc! {"
21764        <span><span></span>ˇ</span>
21765    "});
21766    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21767    cx.assert_editor_state(indoc! {"
21768        <span><span></span>
21769        ˇ</span>
21770    "});
21771
21772    cx.set_state(indoc! {"
21773        <span>ˇ
21774        </span>
21775    "});
21776    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21777    cx.assert_editor_state(indoc! {"
21778        <span>
21779        ˇ
21780        </span>
21781    "});
21782}
21783
21784#[gpui::test(iterations = 10)]
21785async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21786    init_test(cx, |_| {});
21787
21788    let fs = FakeFs::new(cx.executor());
21789    fs.insert_tree(
21790        path!("/dir"),
21791        json!({
21792            "a.ts": "a",
21793        }),
21794    )
21795    .await;
21796
21797    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21798    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21799    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21800
21801    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21802    language_registry.add(Arc::new(Language::new(
21803        LanguageConfig {
21804            name: "TypeScript".into(),
21805            matcher: LanguageMatcher {
21806                path_suffixes: vec!["ts".to_string()],
21807                ..Default::default()
21808            },
21809            ..Default::default()
21810        },
21811        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21812    )));
21813    let mut fake_language_servers = language_registry.register_fake_lsp(
21814        "TypeScript",
21815        FakeLspAdapter {
21816            capabilities: lsp::ServerCapabilities {
21817                code_lens_provider: Some(lsp::CodeLensOptions {
21818                    resolve_provider: Some(true),
21819                }),
21820                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21821                    commands: vec!["_the/command".to_string()],
21822                    ..lsp::ExecuteCommandOptions::default()
21823                }),
21824                ..lsp::ServerCapabilities::default()
21825            },
21826            ..FakeLspAdapter::default()
21827        },
21828    );
21829
21830    let editor = workspace
21831        .update(cx, |workspace, window, cx| {
21832            workspace.open_abs_path(
21833                PathBuf::from(path!("/dir/a.ts")),
21834                OpenOptions::default(),
21835                window,
21836                cx,
21837            )
21838        })
21839        .unwrap()
21840        .await
21841        .unwrap()
21842        .downcast::<Editor>()
21843        .unwrap();
21844    cx.executor().run_until_parked();
21845
21846    let fake_server = fake_language_servers.next().await.unwrap();
21847
21848    let buffer = editor.update(cx, |editor, cx| {
21849        editor
21850            .buffer()
21851            .read(cx)
21852            .as_singleton()
21853            .expect("have opened a single file by path")
21854    });
21855
21856    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21857    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21858    drop(buffer_snapshot);
21859    let actions = cx
21860        .update_window(*workspace, |_, window, cx| {
21861            project.code_actions(&buffer, anchor..anchor, window, cx)
21862        })
21863        .unwrap();
21864
21865    fake_server
21866        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21867            Ok(Some(vec![
21868                lsp::CodeLens {
21869                    range: lsp::Range::default(),
21870                    command: Some(lsp::Command {
21871                        title: "Code lens command".to_owned(),
21872                        command: "_the/command".to_owned(),
21873                        arguments: None,
21874                    }),
21875                    data: None,
21876                },
21877                lsp::CodeLens {
21878                    range: lsp::Range::default(),
21879                    command: Some(lsp::Command {
21880                        title: "Command not in capabilities".to_owned(),
21881                        command: "not in capabilities".to_owned(),
21882                        arguments: None,
21883                    }),
21884                    data: None,
21885                },
21886                lsp::CodeLens {
21887                    range: lsp::Range {
21888                        start: lsp::Position {
21889                            line: 1,
21890                            character: 1,
21891                        },
21892                        end: lsp::Position {
21893                            line: 1,
21894                            character: 1,
21895                        },
21896                    },
21897                    command: Some(lsp::Command {
21898                        title: "Command not in range".to_owned(),
21899                        command: "_the/command".to_owned(),
21900                        arguments: None,
21901                    }),
21902                    data: None,
21903                },
21904            ]))
21905        })
21906        .next()
21907        .await;
21908
21909    let actions = actions.await.unwrap();
21910    assert_eq!(
21911        actions.len(),
21912        1,
21913        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21914    );
21915    let action = actions[0].clone();
21916    let apply = project.update(cx, |project, cx| {
21917        project.apply_code_action(buffer.clone(), action, true, cx)
21918    });
21919
21920    // Resolving the code action does not populate its edits. In absence of
21921    // edits, we must execute the given command.
21922    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21923        |mut lens, _| async move {
21924            let lens_command = lens.command.as_mut().expect("should have a command");
21925            assert_eq!(lens_command.title, "Code lens command");
21926            lens_command.arguments = Some(vec![json!("the-argument")]);
21927            Ok(lens)
21928        },
21929    );
21930
21931    // While executing the command, the language server sends the editor
21932    // a `workspaceEdit` request.
21933    fake_server
21934        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21935            let fake = fake_server.clone();
21936            move |params, _| {
21937                assert_eq!(params.command, "_the/command");
21938                let fake = fake.clone();
21939                async move {
21940                    fake.server
21941                        .request::<lsp::request::ApplyWorkspaceEdit>(
21942                            lsp::ApplyWorkspaceEditParams {
21943                                label: None,
21944                                edit: lsp::WorkspaceEdit {
21945                                    changes: Some(
21946                                        [(
21947                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21948                                            vec![lsp::TextEdit {
21949                                                range: lsp::Range::new(
21950                                                    lsp::Position::new(0, 0),
21951                                                    lsp::Position::new(0, 0),
21952                                                ),
21953                                                new_text: "X".into(),
21954                                            }],
21955                                        )]
21956                                        .into_iter()
21957                                        .collect(),
21958                                    ),
21959                                    ..lsp::WorkspaceEdit::default()
21960                                },
21961                            },
21962                        )
21963                        .await
21964                        .into_response()
21965                        .unwrap();
21966                    Ok(Some(json!(null)))
21967                }
21968            }
21969        })
21970        .next()
21971        .await;
21972
21973    // Applying the code lens command returns a project transaction containing the edits
21974    // sent by the language server in its `workspaceEdit` request.
21975    let transaction = apply.await.unwrap();
21976    assert!(transaction.0.contains_key(&buffer));
21977    buffer.update(cx, |buffer, cx| {
21978        assert_eq!(buffer.text(), "Xa");
21979        buffer.undo(cx);
21980        assert_eq!(buffer.text(), "a");
21981    });
21982
21983    let actions_after_edits = cx
21984        .update_window(*workspace, |_, window, cx| {
21985            project.code_actions(&buffer, anchor..anchor, window, cx)
21986        })
21987        .unwrap()
21988        .await
21989        .unwrap();
21990    assert_eq!(
21991        actions, actions_after_edits,
21992        "For the same selection, same code lens actions should be returned"
21993    );
21994
21995    let _responses =
21996        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21997            panic!("No more code lens requests are expected");
21998        });
21999    editor.update_in(cx, |editor, window, cx| {
22000        editor.select_all(&SelectAll, window, cx);
22001    });
22002    cx.executor().run_until_parked();
22003    let new_actions = cx
22004        .update_window(*workspace, |_, window, cx| {
22005            project.code_actions(&buffer, anchor..anchor, window, cx)
22006        })
22007        .unwrap()
22008        .await
22009        .unwrap();
22010    assert_eq!(
22011        actions, new_actions,
22012        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22013    );
22014}
22015
22016#[gpui::test]
22017async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22018    init_test(cx, |_| {});
22019
22020    let fs = FakeFs::new(cx.executor());
22021    let main_text = r#"fn main() {
22022println!("1");
22023println!("2");
22024println!("3");
22025println!("4");
22026println!("5");
22027}"#;
22028    let lib_text = "mod foo {}";
22029    fs.insert_tree(
22030        path!("/a"),
22031        json!({
22032            "lib.rs": lib_text,
22033            "main.rs": main_text,
22034        }),
22035    )
22036    .await;
22037
22038    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22039    let (workspace, cx) =
22040        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22041    let worktree_id = workspace.update(cx, |workspace, cx| {
22042        workspace.project().update(cx, |project, cx| {
22043            project.worktrees(cx).next().unwrap().read(cx).id()
22044        })
22045    });
22046
22047    let expected_ranges = vec![
22048        Point::new(0, 0)..Point::new(0, 0),
22049        Point::new(1, 0)..Point::new(1, 1),
22050        Point::new(2, 0)..Point::new(2, 2),
22051        Point::new(3, 0)..Point::new(3, 3),
22052    ];
22053
22054    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22055    let editor_1 = workspace
22056        .update_in(cx, |workspace, window, cx| {
22057            workspace.open_path(
22058                (worktree_id, "main.rs"),
22059                Some(pane_1.downgrade()),
22060                true,
22061                window,
22062                cx,
22063            )
22064        })
22065        .unwrap()
22066        .await
22067        .downcast::<Editor>()
22068        .unwrap();
22069    pane_1.update(cx, |pane, cx| {
22070        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22071        open_editor.update(cx, |editor, cx| {
22072            assert_eq!(
22073                editor.display_text(cx),
22074                main_text,
22075                "Original main.rs text on initial open",
22076            );
22077            assert_eq!(
22078                editor
22079                    .selections
22080                    .all::<Point>(cx)
22081                    .into_iter()
22082                    .map(|s| s.range())
22083                    .collect::<Vec<_>>(),
22084                vec![Point::zero()..Point::zero()],
22085                "Default selections on initial open",
22086            );
22087        })
22088    });
22089    editor_1.update_in(cx, |editor, window, cx| {
22090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22091            s.select_ranges(expected_ranges.clone());
22092        });
22093    });
22094
22095    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22096        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22097    });
22098    let editor_2 = workspace
22099        .update_in(cx, |workspace, window, cx| {
22100            workspace.open_path(
22101                (worktree_id, "main.rs"),
22102                Some(pane_2.downgrade()),
22103                true,
22104                window,
22105                cx,
22106            )
22107        })
22108        .unwrap()
22109        .await
22110        .downcast::<Editor>()
22111        .unwrap();
22112    pane_2.update(cx, |pane, cx| {
22113        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22114        open_editor.update(cx, |editor, cx| {
22115            assert_eq!(
22116                editor.display_text(cx),
22117                main_text,
22118                "Original main.rs text on initial open in another panel",
22119            );
22120            assert_eq!(
22121                editor
22122                    .selections
22123                    .all::<Point>(cx)
22124                    .into_iter()
22125                    .map(|s| s.range())
22126                    .collect::<Vec<_>>(),
22127                vec![Point::zero()..Point::zero()],
22128                "Default selections on initial open in another panel",
22129            );
22130        })
22131    });
22132
22133    editor_2.update_in(cx, |editor, window, cx| {
22134        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22135    });
22136
22137    let _other_editor_1 = workspace
22138        .update_in(cx, |workspace, window, cx| {
22139            workspace.open_path(
22140                (worktree_id, "lib.rs"),
22141                Some(pane_1.downgrade()),
22142                true,
22143                window,
22144                cx,
22145            )
22146        })
22147        .unwrap()
22148        .await
22149        .downcast::<Editor>()
22150        .unwrap();
22151    pane_1
22152        .update_in(cx, |pane, window, cx| {
22153            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22154        })
22155        .await
22156        .unwrap();
22157    drop(editor_1);
22158    pane_1.update(cx, |pane, cx| {
22159        pane.active_item()
22160            .unwrap()
22161            .downcast::<Editor>()
22162            .unwrap()
22163            .update(cx, |editor, cx| {
22164                assert_eq!(
22165                    editor.display_text(cx),
22166                    lib_text,
22167                    "Other file should be open and active",
22168                );
22169            });
22170        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22171    });
22172
22173    let _other_editor_2 = workspace
22174        .update_in(cx, |workspace, window, cx| {
22175            workspace.open_path(
22176                (worktree_id, "lib.rs"),
22177                Some(pane_2.downgrade()),
22178                true,
22179                window,
22180                cx,
22181            )
22182        })
22183        .unwrap()
22184        .await
22185        .downcast::<Editor>()
22186        .unwrap();
22187    pane_2
22188        .update_in(cx, |pane, window, cx| {
22189            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22190        })
22191        .await
22192        .unwrap();
22193    drop(editor_2);
22194    pane_2.update(cx, |pane, cx| {
22195        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22196        open_editor.update(cx, |editor, cx| {
22197            assert_eq!(
22198                editor.display_text(cx),
22199                lib_text,
22200                "Other file should be open and active in another panel too",
22201            );
22202        });
22203        assert_eq!(
22204            pane.items().count(),
22205            1,
22206            "No other editors should be open in another pane",
22207        );
22208    });
22209
22210    let _editor_1_reopened = workspace
22211        .update_in(cx, |workspace, window, cx| {
22212            workspace.open_path(
22213                (worktree_id, "main.rs"),
22214                Some(pane_1.downgrade()),
22215                true,
22216                window,
22217                cx,
22218            )
22219        })
22220        .unwrap()
22221        .await
22222        .downcast::<Editor>()
22223        .unwrap();
22224    let _editor_2_reopened = workspace
22225        .update_in(cx, |workspace, window, cx| {
22226            workspace.open_path(
22227                (worktree_id, "main.rs"),
22228                Some(pane_2.downgrade()),
22229                true,
22230                window,
22231                cx,
22232            )
22233        })
22234        .unwrap()
22235        .await
22236        .downcast::<Editor>()
22237        .unwrap();
22238    pane_1.update(cx, |pane, cx| {
22239        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22240        open_editor.update(cx, |editor, cx| {
22241            assert_eq!(
22242                editor.display_text(cx),
22243                main_text,
22244                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22245            );
22246            assert_eq!(
22247                editor
22248                    .selections
22249                    .all::<Point>(cx)
22250                    .into_iter()
22251                    .map(|s| s.range())
22252                    .collect::<Vec<_>>(),
22253                expected_ranges,
22254                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22255            );
22256        })
22257    });
22258    pane_2.update(cx, |pane, cx| {
22259        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22260        open_editor.update(cx, |editor, cx| {
22261            assert_eq!(
22262                editor.display_text(cx),
22263                r#"fn main() {
22264⋯rintln!("1");
22265⋯intln!("2");
22266⋯ntln!("3");
22267println!("4");
22268println!("5");
22269}"#,
22270                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22271            );
22272            assert_eq!(
22273                editor
22274                    .selections
22275                    .all::<Point>(cx)
22276                    .into_iter()
22277                    .map(|s| s.range())
22278                    .collect::<Vec<_>>(),
22279                vec![Point::zero()..Point::zero()],
22280                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22281            );
22282        })
22283    });
22284}
22285
22286#[gpui::test]
22287async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22288    init_test(cx, |_| {});
22289
22290    let fs = FakeFs::new(cx.executor());
22291    let main_text = r#"fn main() {
22292println!("1");
22293println!("2");
22294println!("3");
22295println!("4");
22296println!("5");
22297}"#;
22298    let lib_text = "mod foo {}";
22299    fs.insert_tree(
22300        path!("/a"),
22301        json!({
22302            "lib.rs": lib_text,
22303            "main.rs": main_text,
22304        }),
22305    )
22306    .await;
22307
22308    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22309    let (workspace, cx) =
22310        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22311    let worktree_id = workspace.update(cx, |workspace, cx| {
22312        workspace.project().update(cx, |project, cx| {
22313            project.worktrees(cx).next().unwrap().read(cx).id()
22314        })
22315    });
22316
22317    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22318    let editor = workspace
22319        .update_in(cx, |workspace, window, cx| {
22320            workspace.open_path(
22321                (worktree_id, "main.rs"),
22322                Some(pane.downgrade()),
22323                true,
22324                window,
22325                cx,
22326            )
22327        })
22328        .unwrap()
22329        .await
22330        .downcast::<Editor>()
22331        .unwrap();
22332    pane.update(cx, |pane, cx| {
22333        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22334        open_editor.update(cx, |editor, cx| {
22335            assert_eq!(
22336                editor.display_text(cx),
22337                main_text,
22338                "Original main.rs text on initial open",
22339            );
22340        })
22341    });
22342    editor.update_in(cx, |editor, window, cx| {
22343        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22344    });
22345
22346    cx.update_global(|store: &mut SettingsStore, cx| {
22347        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22348            s.restore_on_file_reopen = Some(false);
22349        });
22350    });
22351    editor.update_in(cx, |editor, window, cx| {
22352        editor.fold_ranges(
22353            vec![
22354                Point::new(1, 0)..Point::new(1, 1),
22355                Point::new(2, 0)..Point::new(2, 2),
22356                Point::new(3, 0)..Point::new(3, 3),
22357            ],
22358            false,
22359            window,
22360            cx,
22361        );
22362    });
22363    pane.update_in(cx, |pane, window, cx| {
22364        pane.close_all_items(&CloseAllItems::default(), window, cx)
22365    })
22366    .await
22367    .unwrap();
22368    pane.update(cx, |pane, _| {
22369        assert!(pane.active_item().is_none());
22370    });
22371    cx.update_global(|store: &mut SettingsStore, cx| {
22372        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22373            s.restore_on_file_reopen = Some(true);
22374        });
22375    });
22376
22377    let _editor_reopened = workspace
22378        .update_in(cx, |workspace, window, cx| {
22379            workspace.open_path(
22380                (worktree_id, "main.rs"),
22381                Some(pane.downgrade()),
22382                true,
22383                window,
22384                cx,
22385            )
22386        })
22387        .unwrap()
22388        .await
22389        .downcast::<Editor>()
22390        .unwrap();
22391    pane.update(cx, |pane, cx| {
22392        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22393        open_editor.update(cx, |editor, cx| {
22394            assert_eq!(
22395                editor.display_text(cx),
22396                main_text,
22397                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22398            );
22399        })
22400    });
22401}
22402
22403#[gpui::test]
22404async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22405    struct EmptyModalView {
22406        focus_handle: gpui::FocusHandle,
22407    }
22408    impl EventEmitter<DismissEvent> for EmptyModalView {}
22409    impl Render for EmptyModalView {
22410        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22411            div()
22412        }
22413    }
22414    impl Focusable for EmptyModalView {
22415        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22416            self.focus_handle.clone()
22417        }
22418    }
22419    impl workspace::ModalView for EmptyModalView {}
22420    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22421        EmptyModalView {
22422            focus_handle: cx.focus_handle(),
22423        }
22424    }
22425
22426    init_test(cx, |_| {});
22427
22428    let fs = FakeFs::new(cx.executor());
22429    let project = Project::test(fs, [], cx).await;
22430    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22431    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22432    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22433    let editor = cx.new_window_entity(|window, cx| {
22434        Editor::new(
22435            EditorMode::full(),
22436            buffer,
22437            Some(project.clone()),
22438            window,
22439            cx,
22440        )
22441    });
22442    workspace
22443        .update(cx, |workspace, window, cx| {
22444            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22445        })
22446        .unwrap();
22447    editor.update_in(cx, |editor, window, cx| {
22448        editor.open_context_menu(&OpenContextMenu, window, cx);
22449        assert!(editor.mouse_context_menu.is_some());
22450    });
22451    workspace
22452        .update(cx, |workspace, window, cx| {
22453            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22454        })
22455        .unwrap();
22456    cx.read(|cx| {
22457        assert!(editor.read(cx).mouse_context_menu.is_none());
22458    });
22459}
22460
22461#[gpui::test]
22462async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22463    init_test(cx, |_| {});
22464
22465    let fs = FakeFs::new(cx.executor());
22466    fs.insert_file(path!("/file.html"), Default::default())
22467        .await;
22468
22469    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22470
22471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22472    let html_language = Arc::new(Language::new(
22473        LanguageConfig {
22474            name: "HTML".into(),
22475            matcher: LanguageMatcher {
22476                path_suffixes: vec!["html".to_string()],
22477                ..LanguageMatcher::default()
22478            },
22479            brackets: BracketPairConfig {
22480                pairs: vec![BracketPair {
22481                    start: "<".into(),
22482                    end: ">".into(),
22483                    close: true,
22484                    ..Default::default()
22485                }],
22486                ..Default::default()
22487            },
22488            ..Default::default()
22489        },
22490        Some(tree_sitter_html::LANGUAGE.into()),
22491    ));
22492    language_registry.add(html_language);
22493    let mut fake_servers = language_registry.register_fake_lsp(
22494        "HTML",
22495        FakeLspAdapter {
22496            capabilities: lsp::ServerCapabilities {
22497                completion_provider: Some(lsp::CompletionOptions {
22498                    resolve_provider: Some(true),
22499                    ..Default::default()
22500                }),
22501                ..Default::default()
22502            },
22503            ..Default::default()
22504        },
22505    );
22506
22507    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22508    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22509
22510    let worktree_id = workspace
22511        .update(cx, |workspace, _window, cx| {
22512            workspace.project().update(cx, |project, cx| {
22513                project.worktrees(cx).next().unwrap().read(cx).id()
22514            })
22515        })
22516        .unwrap();
22517    project
22518        .update(cx, |project, cx| {
22519            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22520        })
22521        .await
22522        .unwrap();
22523    let editor = workspace
22524        .update(cx, |workspace, window, cx| {
22525            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22526        })
22527        .unwrap()
22528        .await
22529        .unwrap()
22530        .downcast::<Editor>()
22531        .unwrap();
22532
22533    let fake_server = fake_servers.next().await.unwrap();
22534    editor.update_in(cx, |editor, window, cx| {
22535        editor.set_text("<ad></ad>", window, cx);
22536        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22537            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22538        });
22539        let Some((buffer, _)) = editor
22540            .buffer
22541            .read(cx)
22542            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22543        else {
22544            panic!("Failed to get buffer for selection position");
22545        };
22546        let buffer = buffer.read(cx);
22547        let buffer_id = buffer.remote_id();
22548        let opening_range =
22549            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22550        let closing_range =
22551            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22552        let mut linked_ranges = HashMap::default();
22553        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
22554        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22555    });
22556    let mut completion_handle =
22557        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22558            Ok(Some(lsp::CompletionResponse::Array(vec![
22559                lsp::CompletionItem {
22560                    label: "head".to_string(),
22561                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22562                        lsp::InsertReplaceEdit {
22563                            new_text: "head".to_string(),
22564                            insert: lsp::Range::new(
22565                                lsp::Position::new(0, 1),
22566                                lsp::Position::new(0, 3),
22567                            ),
22568                            replace: lsp::Range::new(
22569                                lsp::Position::new(0, 1),
22570                                lsp::Position::new(0, 3),
22571                            ),
22572                        },
22573                    )),
22574                    ..Default::default()
22575                },
22576            ])))
22577        });
22578    editor.update_in(cx, |editor, window, cx| {
22579        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22580    });
22581    cx.run_until_parked();
22582    completion_handle.next().await.unwrap();
22583    editor.update(cx, |editor, _| {
22584        assert!(
22585            editor.context_menu_visible(),
22586            "Completion menu should be visible"
22587        );
22588    });
22589    editor.update_in(cx, |editor, window, cx| {
22590        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22591    });
22592    cx.executor().run_until_parked();
22593    editor.update(cx, |editor, cx| {
22594        assert_eq!(editor.text(cx), "<head></head>");
22595    });
22596}
22597
22598#[gpui::test]
22599async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22600    init_test(cx, |_| {});
22601
22602    let fs = FakeFs::new(cx.executor());
22603    fs.insert_tree(
22604        path!("/root"),
22605        json!({
22606            "a": {
22607                "main.rs": "fn main() {}",
22608            },
22609            "foo": {
22610                "bar": {
22611                    "external_file.rs": "pub mod external {}",
22612                }
22613            }
22614        }),
22615    )
22616    .await;
22617
22618    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22619    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22620    language_registry.add(rust_lang());
22621    let _fake_servers = language_registry.register_fake_lsp(
22622        "Rust",
22623        FakeLspAdapter {
22624            ..FakeLspAdapter::default()
22625        },
22626    );
22627    let (workspace, cx) =
22628        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22629    let worktree_id = workspace.update(cx, |workspace, cx| {
22630        workspace.project().update(cx, |project, cx| {
22631            project.worktrees(cx).next().unwrap().read(cx).id()
22632        })
22633    });
22634
22635    let assert_language_servers_count =
22636        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22637            project.update(cx, |project, cx| {
22638                let current = project
22639                    .lsp_store()
22640                    .read(cx)
22641                    .as_local()
22642                    .unwrap()
22643                    .language_servers
22644                    .len();
22645                assert_eq!(expected, current, "{context}");
22646            });
22647        };
22648
22649    assert_language_servers_count(
22650        0,
22651        "No servers should be running before any file is open",
22652        cx,
22653    );
22654    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22655    let main_editor = workspace
22656        .update_in(cx, |workspace, window, cx| {
22657            workspace.open_path(
22658                (worktree_id, "main.rs"),
22659                Some(pane.downgrade()),
22660                true,
22661                window,
22662                cx,
22663            )
22664        })
22665        .unwrap()
22666        .await
22667        .downcast::<Editor>()
22668        .unwrap();
22669    pane.update(cx, |pane, cx| {
22670        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22671        open_editor.update(cx, |editor, cx| {
22672            assert_eq!(
22673                editor.display_text(cx),
22674                "fn main() {}",
22675                "Original main.rs text on initial open",
22676            );
22677        });
22678        assert_eq!(open_editor, main_editor);
22679    });
22680    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22681
22682    let external_editor = workspace
22683        .update_in(cx, |workspace, window, cx| {
22684            workspace.open_abs_path(
22685                PathBuf::from("/root/foo/bar/external_file.rs"),
22686                OpenOptions::default(),
22687                window,
22688                cx,
22689            )
22690        })
22691        .await
22692        .expect("opening external file")
22693        .downcast::<Editor>()
22694        .expect("downcasted external file's open element to editor");
22695    pane.update(cx, |pane, cx| {
22696        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22697        open_editor.update(cx, |editor, cx| {
22698            assert_eq!(
22699                editor.display_text(cx),
22700                "pub mod external {}",
22701                "External file is open now",
22702            );
22703        });
22704        assert_eq!(open_editor, external_editor);
22705    });
22706    assert_language_servers_count(
22707        1,
22708        "Second, external, *.rs file should join the existing server",
22709        cx,
22710    );
22711
22712    pane.update_in(cx, |pane, window, cx| {
22713        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22714    })
22715    .await
22716    .unwrap();
22717    pane.update_in(cx, |pane, window, cx| {
22718        pane.navigate_backward(&Default::default(), window, cx);
22719    });
22720    cx.run_until_parked();
22721    pane.update(cx, |pane, cx| {
22722        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22723        open_editor.update(cx, |editor, cx| {
22724            assert_eq!(
22725                editor.display_text(cx),
22726                "pub mod external {}",
22727                "External file is open now",
22728            );
22729        });
22730    });
22731    assert_language_servers_count(
22732        1,
22733        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22734        cx,
22735    );
22736
22737    cx.update(|_, cx| {
22738        workspace::reload(cx);
22739    });
22740    assert_language_servers_count(
22741        1,
22742        "After reloading the worktree with local and external files opened, only one project should be started",
22743        cx,
22744    );
22745}
22746
22747#[gpui::test]
22748async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22749    init_test(cx, |_| {});
22750
22751    let mut cx = EditorTestContext::new(cx).await;
22752    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22753    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22754
22755    // test cursor move to start of each line on tab
22756    // for `if`, `elif`, `else`, `while`, `with` and `for`
22757    cx.set_state(indoc! {"
22758        def main():
22759        ˇ    for item in items:
22760        ˇ        while item.active:
22761        ˇ            if item.value > 10:
22762        ˇ                continue
22763        ˇ            elif item.value < 0:
22764        ˇ                break
22765        ˇ            else:
22766        ˇ                with item.context() as ctx:
22767        ˇ                    yield count
22768        ˇ        else:
22769        ˇ            log('while else')
22770        ˇ    else:
22771        ˇ        log('for else')
22772    "});
22773    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22774    cx.assert_editor_state(indoc! {"
22775        def main():
22776            ˇfor item in items:
22777                ˇwhile item.active:
22778                    ˇif item.value > 10:
22779                        ˇcontinue
22780                    ˇelif item.value < 0:
22781                        ˇbreak
22782                    ˇelse:
22783                        ˇwith item.context() as ctx:
22784                            ˇyield count
22785                ˇelse:
22786                    ˇlog('while else')
22787            ˇelse:
22788                ˇlog('for else')
22789    "});
22790    // test relative indent is preserved when tab
22791    // for `if`, `elif`, `else`, `while`, `with` and `for`
22792    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22793    cx.assert_editor_state(indoc! {"
22794        def main():
22795                ˇfor item in items:
22796                    ˇwhile item.active:
22797                        ˇif item.value > 10:
22798                            ˇcontinue
22799                        ˇelif item.value < 0:
22800                            ˇbreak
22801                        ˇelse:
22802                            ˇwith item.context() as ctx:
22803                                ˇyield count
22804                    ˇelse:
22805                        ˇlog('while else')
22806                ˇelse:
22807                    ˇlog('for else')
22808    "});
22809
22810    // test cursor move to start of each line on tab
22811    // for `try`, `except`, `else`, `finally`, `match` and `def`
22812    cx.set_state(indoc! {"
22813        def main():
22814        ˇ    try:
22815        ˇ        fetch()
22816        ˇ    except ValueError:
22817        ˇ        handle_error()
22818        ˇ    else:
22819        ˇ        match value:
22820        ˇ            case _:
22821        ˇ    finally:
22822        ˇ        def status():
22823        ˇ            return 0
22824    "});
22825    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22826    cx.assert_editor_state(indoc! {"
22827        def main():
22828            ˇtry:
22829                ˇfetch()
22830            ˇexcept ValueError:
22831                ˇhandle_error()
22832            ˇelse:
22833                ˇmatch value:
22834                    ˇcase _:
22835            ˇfinally:
22836                ˇdef status():
22837                    ˇreturn 0
22838    "});
22839    // test relative indent is preserved when tab
22840    // for `try`, `except`, `else`, `finally`, `match` and `def`
22841    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22842    cx.assert_editor_state(indoc! {"
22843        def main():
22844                ˇtry:
22845                    ˇfetch()
22846                ˇexcept ValueError:
22847                    ˇhandle_error()
22848                ˇelse:
22849                    ˇmatch value:
22850                        ˇcase _:
22851                ˇfinally:
22852                    ˇdef status():
22853                        ˇreturn 0
22854    "});
22855}
22856
22857#[gpui::test]
22858async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22859    init_test(cx, |_| {});
22860
22861    let mut cx = EditorTestContext::new(cx).await;
22862    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22863    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22864
22865    // test `else` auto outdents when typed inside `if` block
22866    cx.set_state(indoc! {"
22867        def main():
22868            if i == 2:
22869                return
22870                ˇ
22871    "});
22872    cx.update_editor(|editor, window, cx| {
22873        editor.handle_input("else:", window, cx);
22874    });
22875    cx.assert_editor_state(indoc! {"
22876        def main():
22877            if i == 2:
22878                return
22879            else:ˇ
22880    "});
22881
22882    // test `except` auto outdents when typed inside `try` block
22883    cx.set_state(indoc! {"
22884        def main():
22885            try:
22886                i = 2
22887                ˇ
22888    "});
22889    cx.update_editor(|editor, window, cx| {
22890        editor.handle_input("except:", window, cx);
22891    });
22892    cx.assert_editor_state(indoc! {"
22893        def main():
22894            try:
22895                i = 2
22896            except:ˇ
22897    "});
22898
22899    // test `else` auto outdents when typed inside `except` block
22900    cx.set_state(indoc! {"
22901        def main():
22902            try:
22903                i = 2
22904            except:
22905                j = 2
22906                ˇ
22907    "});
22908    cx.update_editor(|editor, window, cx| {
22909        editor.handle_input("else:", window, cx);
22910    });
22911    cx.assert_editor_state(indoc! {"
22912        def main():
22913            try:
22914                i = 2
22915            except:
22916                j = 2
22917            else:ˇ
22918    "});
22919
22920    // test `finally` auto outdents when typed inside `else` block
22921    cx.set_state(indoc! {"
22922        def main():
22923            try:
22924                i = 2
22925            except:
22926                j = 2
22927            else:
22928                k = 2
22929                ˇ
22930    "});
22931    cx.update_editor(|editor, window, cx| {
22932        editor.handle_input("finally:", window, cx);
22933    });
22934    cx.assert_editor_state(indoc! {"
22935        def main():
22936            try:
22937                i = 2
22938            except:
22939                j = 2
22940            else:
22941                k = 2
22942            finally:ˇ
22943    "});
22944
22945    // test `else` does not outdents when typed inside `except` block right after for block
22946    cx.set_state(indoc! {"
22947        def main():
22948            try:
22949                i = 2
22950            except:
22951                for i in range(n):
22952                    pass
22953                ˇ
22954    "});
22955    cx.update_editor(|editor, window, cx| {
22956        editor.handle_input("else:", window, cx);
22957    });
22958    cx.assert_editor_state(indoc! {"
22959        def main():
22960            try:
22961                i = 2
22962            except:
22963                for i in range(n):
22964                    pass
22965                else:ˇ
22966    "});
22967
22968    // test `finally` auto outdents when typed inside `else` block right after for block
22969    cx.set_state(indoc! {"
22970        def main():
22971            try:
22972                i = 2
22973            except:
22974                j = 2
22975            else:
22976                for i in range(n):
22977                    pass
22978                ˇ
22979    "});
22980    cx.update_editor(|editor, window, cx| {
22981        editor.handle_input("finally:", window, cx);
22982    });
22983    cx.assert_editor_state(indoc! {"
22984        def main():
22985            try:
22986                i = 2
22987            except:
22988                j = 2
22989            else:
22990                for i in range(n):
22991                    pass
22992            finally:ˇ
22993    "});
22994
22995    // test `except` outdents to inner "try" block
22996    cx.set_state(indoc! {"
22997        def main():
22998            try:
22999                i = 2
23000                if i == 2:
23001                    try:
23002                        i = 3
23003                        ˇ
23004    "});
23005    cx.update_editor(|editor, window, cx| {
23006        editor.handle_input("except:", window, cx);
23007    });
23008    cx.assert_editor_state(indoc! {"
23009        def main():
23010            try:
23011                i = 2
23012                if i == 2:
23013                    try:
23014                        i = 3
23015                    except:ˇ
23016    "});
23017
23018    // test `except` outdents to outer "try" block
23019    cx.set_state(indoc! {"
23020        def main():
23021            try:
23022                i = 2
23023                if i == 2:
23024                    try:
23025                        i = 3
23026                ˇ
23027    "});
23028    cx.update_editor(|editor, window, cx| {
23029        editor.handle_input("except:", window, cx);
23030    });
23031    cx.assert_editor_state(indoc! {"
23032        def main():
23033            try:
23034                i = 2
23035                if i == 2:
23036                    try:
23037                        i = 3
23038            except:ˇ
23039    "});
23040
23041    // test `else` stays at correct indent when typed after `for` block
23042    cx.set_state(indoc! {"
23043        def main():
23044            for i in range(10):
23045                if i == 3:
23046                    break
23047            ˇ
23048    "});
23049    cx.update_editor(|editor, window, cx| {
23050        editor.handle_input("else:", window, cx);
23051    });
23052    cx.assert_editor_state(indoc! {"
23053        def main():
23054            for i in range(10):
23055                if i == 3:
23056                    break
23057            else:ˇ
23058    "});
23059
23060    // test does not outdent on typing after line with square brackets
23061    cx.set_state(indoc! {"
23062        def f() -> list[str]:
23063            ˇ
23064    "});
23065    cx.update_editor(|editor, window, cx| {
23066        editor.handle_input("a", window, cx);
23067    });
23068    cx.assert_editor_state(indoc! {"
23069        def f() -> list[str]:
2307023071    "});
23072
23073    // test does not outdent on typing : after case keyword
23074    cx.set_state(indoc! {"
23075        match 1:
23076            caseˇ
23077    "});
23078    cx.update_editor(|editor, window, cx| {
23079        editor.handle_input(":", window, cx);
23080    });
23081    cx.assert_editor_state(indoc! {"
23082        match 1:
23083            case:ˇ
23084    "});
23085}
23086
23087#[gpui::test]
23088async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23089    init_test(cx, |_| {});
23090    update_test_language_settings(cx, |settings| {
23091        settings.defaults.extend_comment_on_newline = Some(false);
23092    });
23093    let mut cx = EditorTestContext::new(cx).await;
23094    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23096
23097    // test correct indent after newline on comment
23098    cx.set_state(indoc! {"
23099        # COMMENT:ˇ
23100    "});
23101    cx.update_editor(|editor, window, cx| {
23102        editor.newline(&Newline, window, cx);
23103    });
23104    cx.assert_editor_state(indoc! {"
23105        # COMMENT:
23106        ˇ
23107    "});
23108
23109    // test correct indent after newline in brackets
23110    cx.set_state(indoc! {"
23111        {ˇ}
23112    "});
23113    cx.update_editor(|editor, window, cx| {
23114        editor.newline(&Newline, window, cx);
23115    });
23116    cx.run_until_parked();
23117    cx.assert_editor_state(indoc! {"
23118        {
23119            ˇ
23120        }
23121    "});
23122
23123    cx.set_state(indoc! {"
23124        (ˇ)
23125    "});
23126    cx.update_editor(|editor, window, cx| {
23127        editor.newline(&Newline, window, cx);
23128    });
23129    cx.run_until_parked();
23130    cx.assert_editor_state(indoc! {"
23131        (
23132            ˇ
23133        )
23134    "});
23135
23136    // do not indent after empty lists or dictionaries
23137    cx.set_state(indoc! {"
23138        a = []ˇ
23139    "});
23140    cx.update_editor(|editor, window, cx| {
23141        editor.newline(&Newline, window, cx);
23142    });
23143    cx.run_until_parked();
23144    cx.assert_editor_state(indoc! {"
23145        a = []
23146        ˇ
23147    "});
23148}
23149
23150#[gpui::test]
23151async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23152    init_test(cx, |_| {});
23153
23154    let mut cx = EditorTestContext::new(cx).await;
23155    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23156    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23157
23158    // test cursor move to start of each line on tab
23159    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23160    cx.set_state(indoc! {"
23161        function main() {
23162        ˇ    for item in $items; do
23163        ˇ        while [ -n \"$item\" ]; do
23164        ˇ            if [ \"$value\" -gt 10 ]; then
23165        ˇ                continue
23166        ˇ            elif [ \"$value\" -lt 0 ]; then
23167        ˇ                break
23168        ˇ            else
23169        ˇ                echo \"$item\"
23170        ˇ            fi
23171        ˇ        done
23172        ˇ    done
23173        ˇ}
23174    "});
23175    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23176    cx.assert_editor_state(indoc! {"
23177        function main() {
23178            ˇfor item in $items; do
23179                ˇwhile [ -n \"$item\" ]; do
23180                    ˇif [ \"$value\" -gt 10 ]; then
23181                        ˇcontinue
23182                    ˇelif [ \"$value\" -lt 0 ]; then
23183                        ˇbreak
23184                    ˇelse
23185                        ˇecho \"$item\"
23186                    ˇfi
23187                ˇdone
23188            ˇdone
23189        ˇ}
23190    "});
23191    // test relative indent is preserved when tab
23192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23193    cx.assert_editor_state(indoc! {"
23194        function main() {
23195                ˇfor item in $items; do
23196                    ˇwhile [ -n \"$item\" ]; do
23197                        ˇif [ \"$value\" -gt 10 ]; then
23198                            ˇcontinue
23199                        ˇelif [ \"$value\" -lt 0 ]; then
23200                            ˇbreak
23201                        ˇelse
23202                            ˇecho \"$item\"
23203                        ˇfi
23204                    ˇdone
23205                ˇdone
23206            ˇ}
23207    "});
23208
23209    // test cursor move to start of each line on tab
23210    // for `case` statement with patterns
23211    cx.set_state(indoc! {"
23212        function handle() {
23213        ˇ    case \"$1\" in
23214        ˇ        start)
23215        ˇ            echo \"a\"
23216        ˇ            ;;
23217        ˇ        stop)
23218        ˇ            echo \"b\"
23219        ˇ            ;;
23220        ˇ        *)
23221        ˇ            echo \"c\"
23222        ˇ            ;;
23223        ˇ    esac
23224        ˇ}
23225    "});
23226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23227    cx.assert_editor_state(indoc! {"
23228        function handle() {
23229            ˇcase \"$1\" in
23230                ˇstart)
23231                    ˇecho \"a\"
23232                    ˇ;;
23233                ˇstop)
23234                    ˇecho \"b\"
23235                    ˇ;;
23236                ˇ*)
23237                    ˇecho \"c\"
23238                    ˇ;;
23239            ˇesac
23240        ˇ}
23241    "});
23242}
23243
23244#[gpui::test]
23245async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23246    init_test(cx, |_| {});
23247
23248    let mut cx = EditorTestContext::new(cx).await;
23249    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23250    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23251
23252    // test indents on comment insert
23253    cx.set_state(indoc! {"
23254        function main() {
23255        ˇ    for item in $items; do
23256        ˇ        while [ -n \"$item\" ]; do
23257        ˇ            if [ \"$value\" -gt 10 ]; then
23258        ˇ                continue
23259        ˇ            elif [ \"$value\" -lt 0 ]; then
23260        ˇ                break
23261        ˇ            else
23262        ˇ                echo \"$item\"
23263        ˇ            fi
23264        ˇ        done
23265        ˇ    done
23266        ˇ}
23267    "});
23268    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23269    cx.assert_editor_state(indoc! {"
23270        function main() {
23271        #ˇ    for item in $items; do
23272        #ˇ        while [ -n \"$item\" ]; do
23273        #ˇ            if [ \"$value\" -gt 10 ]; then
23274        #ˇ                continue
23275        #ˇ            elif [ \"$value\" -lt 0 ]; then
23276        #ˇ                break
23277        #ˇ            else
23278        #ˇ                echo \"$item\"
23279        #ˇ            fi
23280        #ˇ        done
23281        #ˇ    done
23282        #ˇ}
23283    "});
23284}
23285
23286#[gpui::test]
23287async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23288    init_test(cx, |_| {});
23289
23290    let mut cx = EditorTestContext::new(cx).await;
23291    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23292    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23293
23294    // test `else` auto outdents when typed inside `if` block
23295    cx.set_state(indoc! {"
23296        if [ \"$1\" = \"test\" ]; then
23297            echo \"foo bar\"
23298            ˇ
23299    "});
23300    cx.update_editor(|editor, window, cx| {
23301        editor.handle_input("else", window, cx);
23302    });
23303    cx.assert_editor_state(indoc! {"
23304        if [ \"$1\" = \"test\" ]; then
23305            echo \"foo bar\"
23306        elseˇ
23307    "});
23308
23309    // test `elif` auto outdents when typed inside `if` block
23310    cx.set_state(indoc! {"
23311        if [ \"$1\" = \"test\" ]; then
23312            echo \"foo bar\"
23313            ˇ
23314    "});
23315    cx.update_editor(|editor, window, cx| {
23316        editor.handle_input("elif", window, cx);
23317    });
23318    cx.assert_editor_state(indoc! {"
23319        if [ \"$1\" = \"test\" ]; then
23320            echo \"foo bar\"
23321        elifˇ
23322    "});
23323
23324    // test `fi` auto outdents when typed inside `else` block
23325    cx.set_state(indoc! {"
23326        if [ \"$1\" = \"test\" ]; then
23327            echo \"foo bar\"
23328        else
23329            echo \"bar baz\"
23330            ˇ
23331    "});
23332    cx.update_editor(|editor, window, cx| {
23333        editor.handle_input("fi", window, cx);
23334    });
23335    cx.assert_editor_state(indoc! {"
23336        if [ \"$1\" = \"test\" ]; then
23337            echo \"foo bar\"
23338        else
23339            echo \"bar baz\"
23340        fiˇ
23341    "});
23342
23343    // test `done` auto outdents when typed inside `while` block
23344    cx.set_state(indoc! {"
23345        while read line; do
23346            echo \"$line\"
23347            ˇ
23348    "});
23349    cx.update_editor(|editor, window, cx| {
23350        editor.handle_input("done", window, cx);
23351    });
23352    cx.assert_editor_state(indoc! {"
23353        while read line; do
23354            echo \"$line\"
23355        doneˇ
23356    "});
23357
23358    // test `done` auto outdents when typed inside `for` block
23359    cx.set_state(indoc! {"
23360        for file in *.txt; do
23361            cat \"$file\"
23362            ˇ
23363    "});
23364    cx.update_editor(|editor, window, cx| {
23365        editor.handle_input("done", window, cx);
23366    });
23367    cx.assert_editor_state(indoc! {"
23368        for file in *.txt; do
23369            cat \"$file\"
23370        doneˇ
23371    "});
23372
23373    // test `esac` auto outdents when typed inside `case` block
23374    cx.set_state(indoc! {"
23375        case \"$1\" in
23376            start)
23377                echo \"foo bar\"
23378                ;;
23379            stop)
23380                echo \"bar baz\"
23381                ;;
23382            ˇ
23383    "});
23384    cx.update_editor(|editor, window, cx| {
23385        editor.handle_input("esac", window, cx);
23386    });
23387    cx.assert_editor_state(indoc! {"
23388        case \"$1\" in
23389            start)
23390                echo \"foo bar\"
23391                ;;
23392            stop)
23393                echo \"bar baz\"
23394                ;;
23395        esacˇ
23396    "});
23397
23398    // test `*)` auto outdents when typed inside `case` block
23399    cx.set_state(indoc! {"
23400        case \"$1\" in
23401            start)
23402                echo \"foo bar\"
23403                ;;
23404                ˇ
23405    "});
23406    cx.update_editor(|editor, window, cx| {
23407        editor.handle_input("*)", window, cx);
23408    });
23409    cx.assert_editor_state(indoc! {"
23410        case \"$1\" in
23411            start)
23412                echo \"foo bar\"
23413                ;;
23414            *)ˇ
23415    "});
23416
23417    // test `fi` outdents to correct level with nested if blocks
23418    cx.set_state(indoc! {"
23419        if [ \"$1\" = \"test\" ]; then
23420            echo \"outer if\"
23421            if [ \"$2\" = \"debug\" ]; then
23422                echo \"inner if\"
23423                ˇ
23424    "});
23425    cx.update_editor(|editor, window, cx| {
23426        editor.handle_input("fi", window, cx);
23427    });
23428    cx.assert_editor_state(indoc! {"
23429        if [ \"$1\" = \"test\" ]; then
23430            echo \"outer if\"
23431            if [ \"$2\" = \"debug\" ]; then
23432                echo \"inner if\"
23433            fiˇ
23434    "});
23435}
23436
23437#[gpui::test]
23438async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23439    init_test(cx, |_| {});
23440    update_test_language_settings(cx, |settings| {
23441        settings.defaults.extend_comment_on_newline = Some(false);
23442    });
23443    let mut cx = EditorTestContext::new(cx).await;
23444    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23445    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23446
23447    // test correct indent after newline on comment
23448    cx.set_state(indoc! {"
23449        # COMMENT:ˇ
23450    "});
23451    cx.update_editor(|editor, window, cx| {
23452        editor.newline(&Newline, window, cx);
23453    });
23454    cx.assert_editor_state(indoc! {"
23455        # COMMENT:
23456        ˇ
23457    "});
23458
23459    // test correct indent after newline after `then`
23460    cx.set_state(indoc! {"
23461
23462        if [ \"$1\" = \"test\" ]; thenˇ
23463    "});
23464    cx.update_editor(|editor, window, cx| {
23465        editor.newline(&Newline, window, cx);
23466    });
23467    cx.run_until_parked();
23468    cx.assert_editor_state(indoc! {"
23469
23470        if [ \"$1\" = \"test\" ]; then
23471            ˇ
23472    "});
23473
23474    // test correct indent after newline after `else`
23475    cx.set_state(indoc! {"
23476        if [ \"$1\" = \"test\" ]; then
23477        elseˇ
23478    "});
23479    cx.update_editor(|editor, window, cx| {
23480        editor.newline(&Newline, window, cx);
23481    });
23482    cx.run_until_parked();
23483    cx.assert_editor_state(indoc! {"
23484        if [ \"$1\" = \"test\" ]; then
23485        else
23486            ˇ
23487    "});
23488
23489    // test correct indent after newline after `elif`
23490    cx.set_state(indoc! {"
23491        if [ \"$1\" = \"test\" ]; then
23492        elifˇ
23493    "});
23494    cx.update_editor(|editor, window, cx| {
23495        editor.newline(&Newline, window, cx);
23496    });
23497    cx.run_until_parked();
23498    cx.assert_editor_state(indoc! {"
23499        if [ \"$1\" = \"test\" ]; then
23500        elif
23501            ˇ
23502    "});
23503
23504    // test correct indent after newline after `do`
23505    cx.set_state(indoc! {"
23506        for file in *.txt; doˇ
23507    "});
23508    cx.update_editor(|editor, window, cx| {
23509        editor.newline(&Newline, window, cx);
23510    });
23511    cx.run_until_parked();
23512    cx.assert_editor_state(indoc! {"
23513        for file in *.txt; do
23514            ˇ
23515    "});
23516
23517    // test correct indent after newline after case pattern
23518    cx.set_state(indoc! {"
23519        case \"$1\" in
23520            start)ˇ
23521    "});
23522    cx.update_editor(|editor, window, cx| {
23523        editor.newline(&Newline, window, cx);
23524    });
23525    cx.run_until_parked();
23526    cx.assert_editor_state(indoc! {"
23527        case \"$1\" in
23528            start)
23529                ˇ
23530    "});
23531
23532    // test correct indent after newline after case pattern
23533    cx.set_state(indoc! {"
23534        case \"$1\" in
23535            start)
23536                ;;
23537            *)ˇ
23538    "});
23539    cx.update_editor(|editor, window, cx| {
23540        editor.newline(&Newline, window, cx);
23541    });
23542    cx.run_until_parked();
23543    cx.assert_editor_state(indoc! {"
23544        case \"$1\" in
23545            start)
23546                ;;
23547            *)
23548                ˇ
23549    "});
23550
23551    // test correct indent after newline after function opening brace
23552    cx.set_state(indoc! {"
23553        function test() {ˇ}
23554    "});
23555    cx.update_editor(|editor, window, cx| {
23556        editor.newline(&Newline, window, cx);
23557    });
23558    cx.run_until_parked();
23559    cx.assert_editor_state(indoc! {"
23560        function test() {
23561            ˇ
23562        }
23563    "});
23564
23565    // test no extra indent after semicolon on same line
23566    cx.set_state(indoc! {"
23567        echo \"test\"23568    "});
23569    cx.update_editor(|editor, window, cx| {
23570        editor.newline(&Newline, window, cx);
23571    });
23572    cx.run_until_parked();
23573    cx.assert_editor_state(indoc! {"
23574        echo \"test\";
23575        ˇ
23576    "});
23577}
23578
23579fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23580    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23581    point..point
23582}
23583
23584#[track_caller]
23585fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23586    let (text, ranges) = marked_text_ranges(marked_text, true);
23587    assert_eq!(editor.text(cx), text);
23588    assert_eq!(
23589        editor.selections.ranges(cx),
23590        ranges,
23591        "Assert selections are {}",
23592        marked_text
23593    );
23594}
23595
23596pub fn handle_signature_help_request(
23597    cx: &mut EditorLspTestContext,
23598    mocked_response: lsp::SignatureHelp,
23599) -> impl Future<Output = ()> + use<> {
23600    let mut request =
23601        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23602            let mocked_response = mocked_response.clone();
23603            async move { Ok(Some(mocked_response)) }
23604        });
23605
23606    async move {
23607        request.next().await;
23608    }
23609}
23610
23611#[track_caller]
23612pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23613    cx.update_editor(|editor, _, _| {
23614        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23615            let entries = menu.entries.borrow();
23616            let entries = entries
23617                .iter()
23618                .map(|entry| entry.string.as_str())
23619                .collect::<Vec<_>>();
23620            assert_eq!(entries, expected);
23621        } else {
23622            panic!("Expected completions menu");
23623        }
23624    });
23625}
23626
23627/// Handle completion request passing a marked string specifying where the completion
23628/// should be triggered from using '|' character, what range should be replaced, and what completions
23629/// should be returned using '<' and '>' to delimit the range.
23630///
23631/// Also see `handle_completion_request_with_insert_and_replace`.
23632#[track_caller]
23633pub fn handle_completion_request(
23634    marked_string: &str,
23635    completions: Vec<&'static str>,
23636    is_incomplete: bool,
23637    counter: Arc<AtomicUsize>,
23638    cx: &mut EditorLspTestContext,
23639) -> impl Future<Output = ()> {
23640    let complete_from_marker: TextRangeMarker = '|'.into();
23641    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23642    let (_, mut marked_ranges) = marked_text_ranges_by(
23643        marked_string,
23644        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23645    );
23646
23647    let complete_from_position =
23648        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23649    let replace_range =
23650        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23651
23652    let mut request =
23653        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23654            let completions = completions.clone();
23655            counter.fetch_add(1, atomic::Ordering::Release);
23656            async move {
23657                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23658                assert_eq!(
23659                    params.text_document_position.position,
23660                    complete_from_position
23661                );
23662                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23663                    is_incomplete,
23664                    item_defaults: None,
23665                    items: completions
23666                        .iter()
23667                        .map(|completion_text| lsp::CompletionItem {
23668                            label: completion_text.to_string(),
23669                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23670                                range: replace_range,
23671                                new_text: completion_text.to_string(),
23672                            })),
23673                            ..Default::default()
23674                        })
23675                        .collect(),
23676                })))
23677            }
23678        });
23679
23680    async move {
23681        request.next().await;
23682    }
23683}
23684
23685/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23686/// given instead, which also contains an `insert` range.
23687///
23688/// This function uses markers to define ranges:
23689/// - `|` marks the cursor position
23690/// - `<>` marks the replace range
23691/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23692pub fn handle_completion_request_with_insert_and_replace(
23693    cx: &mut EditorLspTestContext,
23694    marked_string: &str,
23695    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23696    counter: Arc<AtomicUsize>,
23697) -> impl Future<Output = ()> {
23698    let complete_from_marker: TextRangeMarker = '|'.into();
23699    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23700    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23701
23702    let (_, mut marked_ranges) = marked_text_ranges_by(
23703        marked_string,
23704        vec![
23705            complete_from_marker.clone(),
23706            replace_range_marker.clone(),
23707            insert_range_marker.clone(),
23708        ],
23709    );
23710
23711    let complete_from_position =
23712        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23713    let replace_range =
23714        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23715
23716    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23717        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23718        _ => lsp::Range {
23719            start: replace_range.start,
23720            end: complete_from_position,
23721        },
23722    };
23723
23724    let mut request =
23725        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23726            let completions = completions.clone();
23727            counter.fetch_add(1, atomic::Ordering::Release);
23728            async move {
23729                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23730                assert_eq!(
23731                    params.text_document_position.position, complete_from_position,
23732                    "marker `|` position doesn't match",
23733                );
23734                Ok(Some(lsp::CompletionResponse::Array(
23735                    completions
23736                        .iter()
23737                        .map(|(label, new_text)| lsp::CompletionItem {
23738                            label: label.to_string(),
23739                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23740                                lsp::InsertReplaceEdit {
23741                                    insert: insert_range,
23742                                    replace: replace_range,
23743                                    new_text: new_text.to_string(),
23744                                },
23745                            )),
23746                            ..Default::default()
23747                        })
23748                        .collect(),
23749                )))
23750            }
23751        });
23752
23753    async move {
23754        request.next().await;
23755    }
23756}
23757
23758fn handle_resolve_completion_request(
23759    cx: &mut EditorLspTestContext,
23760    edits: Option<Vec<(&'static str, &'static str)>>,
23761) -> impl Future<Output = ()> {
23762    let edits = edits.map(|edits| {
23763        edits
23764            .iter()
23765            .map(|(marked_string, new_text)| {
23766                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23767                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23768                lsp::TextEdit::new(replace_range, new_text.to_string())
23769            })
23770            .collect::<Vec<_>>()
23771    });
23772
23773    let mut request =
23774        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23775            let edits = edits.clone();
23776            async move {
23777                Ok(lsp::CompletionItem {
23778                    additional_text_edits: edits,
23779                    ..Default::default()
23780                })
23781            }
23782        });
23783
23784    async move {
23785        request.next().await;
23786    }
23787}
23788
23789pub(crate) fn update_test_language_settings(
23790    cx: &mut TestAppContext,
23791    f: impl Fn(&mut AllLanguageSettingsContent),
23792) {
23793    cx.update(|cx| {
23794        SettingsStore::update_global(cx, |store, cx| {
23795            store.update_user_settings::<AllLanguageSettings>(cx, f);
23796        });
23797    });
23798}
23799
23800pub(crate) fn update_test_project_settings(
23801    cx: &mut TestAppContext,
23802    f: impl Fn(&mut ProjectSettings),
23803) {
23804    cx.update(|cx| {
23805        SettingsStore::update_global(cx, |store, cx| {
23806            store.update_user_settings::<ProjectSettings>(cx, f);
23807        });
23808    });
23809}
23810
23811pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23812    cx.update(|cx| {
23813        assets::Assets.load_test_fonts(cx);
23814        let store = SettingsStore::test(cx);
23815        cx.set_global(store);
23816        theme::init(theme::LoadThemes::JustBase, cx);
23817        release_channel::init(SemanticVersion::default(), cx);
23818        client::init_settings(cx);
23819        language::init(cx);
23820        Project::init_settings(cx);
23821        workspace::init_settings(cx);
23822        crate::init(cx);
23823    });
23824    zlog::init_test();
23825    update_test_language_settings(cx, f);
23826}
23827
23828#[track_caller]
23829fn assert_hunk_revert(
23830    not_reverted_text_with_selections: &str,
23831    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23832    expected_reverted_text_with_selections: &str,
23833    base_text: &str,
23834    cx: &mut EditorLspTestContext,
23835) {
23836    cx.set_state(not_reverted_text_with_selections);
23837    cx.set_head_text(base_text);
23838    cx.executor().run_until_parked();
23839
23840    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23841        let snapshot = editor.snapshot(window, cx);
23842        let reverted_hunk_statuses = snapshot
23843            .buffer_snapshot
23844            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23845            .map(|hunk| hunk.status().kind)
23846            .collect::<Vec<_>>();
23847
23848        editor.git_restore(&Default::default(), window, cx);
23849        reverted_hunk_statuses
23850    });
23851    cx.executor().run_until_parked();
23852    cx.assert_editor_state(expected_reverted_text_with_selections);
23853    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23854}
23855
23856#[gpui::test(iterations = 10)]
23857async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23858    init_test(cx, |_| {});
23859
23860    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23861    let counter = diagnostic_requests.clone();
23862
23863    let fs = FakeFs::new(cx.executor());
23864    fs.insert_tree(
23865        path!("/a"),
23866        json!({
23867            "first.rs": "fn main() { let a = 5; }",
23868            "second.rs": "// Test file",
23869        }),
23870    )
23871    .await;
23872
23873    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23874    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23875    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23876
23877    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23878    language_registry.add(rust_lang());
23879    let mut fake_servers = language_registry.register_fake_lsp(
23880        "Rust",
23881        FakeLspAdapter {
23882            capabilities: lsp::ServerCapabilities {
23883                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23884                    lsp::DiagnosticOptions {
23885                        identifier: None,
23886                        inter_file_dependencies: true,
23887                        workspace_diagnostics: true,
23888                        work_done_progress_options: Default::default(),
23889                    },
23890                )),
23891                ..Default::default()
23892            },
23893            ..Default::default()
23894        },
23895    );
23896
23897    let editor = workspace
23898        .update(cx, |workspace, window, cx| {
23899            workspace.open_abs_path(
23900                PathBuf::from(path!("/a/first.rs")),
23901                OpenOptions::default(),
23902                window,
23903                cx,
23904            )
23905        })
23906        .unwrap()
23907        .await
23908        .unwrap()
23909        .downcast::<Editor>()
23910        .unwrap();
23911    let fake_server = fake_servers.next().await.unwrap();
23912    let server_id = fake_server.server.server_id();
23913    let mut first_request = fake_server
23914        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23915            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23916            let result_id = Some(new_result_id.to_string());
23917            assert_eq!(
23918                params.text_document.uri,
23919                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23920            );
23921            async move {
23922                Ok(lsp::DocumentDiagnosticReportResult::Report(
23923                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23924                        related_documents: None,
23925                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23926                            items: Vec::new(),
23927                            result_id,
23928                        },
23929                    }),
23930                ))
23931            }
23932        });
23933
23934    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23935        project.update(cx, |project, cx| {
23936            let buffer_id = editor
23937                .read(cx)
23938                .buffer()
23939                .read(cx)
23940                .as_singleton()
23941                .expect("created a singleton buffer")
23942                .read(cx)
23943                .remote_id();
23944            let buffer_result_id = project
23945                .lsp_store()
23946                .read(cx)
23947                .result_id(server_id, buffer_id, cx);
23948            assert_eq!(expected, buffer_result_id);
23949        });
23950    };
23951
23952    ensure_result_id(None, cx);
23953    cx.executor().advance_clock(Duration::from_millis(60));
23954    cx.executor().run_until_parked();
23955    assert_eq!(
23956        diagnostic_requests.load(atomic::Ordering::Acquire),
23957        1,
23958        "Opening file should trigger diagnostic request"
23959    );
23960    first_request
23961        .next()
23962        .await
23963        .expect("should have sent the first diagnostics pull request");
23964    ensure_result_id(Some("1".to_string()), cx);
23965
23966    // Editing should trigger diagnostics
23967    editor.update_in(cx, |editor, window, cx| {
23968        editor.handle_input("2", window, cx)
23969    });
23970    cx.executor().advance_clock(Duration::from_millis(60));
23971    cx.executor().run_until_parked();
23972    assert_eq!(
23973        diagnostic_requests.load(atomic::Ordering::Acquire),
23974        2,
23975        "Editing should trigger diagnostic request"
23976    );
23977    ensure_result_id(Some("2".to_string()), cx);
23978
23979    // Moving cursor should not trigger diagnostic request
23980    editor.update_in(cx, |editor, window, cx| {
23981        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23982            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23983        });
23984    });
23985    cx.executor().advance_clock(Duration::from_millis(60));
23986    cx.executor().run_until_parked();
23987    assert_eq!(
23988        diagnostic_requests.load(atomic::Ordering::Acquire),
23989        2,
23990        "Cursor movement should not trigger diagnostic request"
23991    );
23992    ensure_result_id(Some("2".to_string()), cx);
23993    // Multiple rapid edits should be debounced
23994    for _ in 0..5 {
23995        editor.update_in(cx, |editor, window, cx| {
23996            editor.handle_input("x", window, cx)
23997        });
23998    }
23999    cx.executor().advance_clock(Duration::from_millis(60));
24000    cx.executor().run_until_parked();
24001
24002    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24003    assert!(
24004        final_requests <= 4,
24005        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24006    );
24007    ensure_result_id(Some(final_requests.to_string()), cx);
24008}
24009
24010#[gpui::test]
24011async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24012    // Regression test for issue #11671
24013    // Previously, adding a cursor after moving multiple cursors would reset
24014    // the cursor count instead of adding to the existing cursors.
24015    init_test(cx, |_| {});
24016    let mut cx = EditorTestContext::new(cx).await;
24017
24018    // Create a simple buffer with cursor at start
24019    cx.set_state(indoc! {"
24020        ˇaaaa
24021        bbbb
24022        cccc
24023        dddd
24024        eeee
24025        ffff
24026        gggg
24027        hhhh"});
24028
24029    // Add 2 cursors below (so we have 3 total)
24030    cx.update_editor(|editor, window, cx| {
24031        editor.add_selection_below(&Default::default(), window, cx);
24032        editor.add_selection_below(&Default::default(), window, cx);
24033    });
24034
24035    // Verify we have 3 cursors
24036    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24037    assert_eq!(
24038        initial_count, 3,
24039        "Should have 3 cursors after adding 2 below"
24040    );
24041
24042    // Move down one line
24043    cx.update_editor(|editor, window, cx| {
24044        editor.move_down(&MoveDown, window, cx);
24045    });
24046
24047    // Add another cursor below
24048    cx.update_editor(|editor, window, cx| {
24049        editor.add_selection_below(&Default::default(), window, cx);
24050    });
24051
24052    // Should now have 4 cursors (3 original + 1 new)
24053    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24054    assert_eq!(
24055        final_count, 4,
24056        "Should have 4 cursors after moving and adding another"
24057    );
24058}
24059
24060#[gpui::test(iterations = 10)]
24061async fn test_document_colors(cx: &mut TestAppContext) {
24062    let expected_color = Rgba {
24063        r: 0.33,
24064        g: 0.33,
24065        b: 0.33,
24066        a: 0.33,
24067    };
24068
24069    init_test(cx, |_| {});
24070
24071    let fs = FakeFs::new(cx.executor());
24072    fs.insert_tree(
24073        path!("/a"),
24074        json!({
24075            "first.rs": "fn main() { let a = 5; }",
24076        }),
24077    )
24078    .await;
24079
24080    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24081    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24082    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24083
24084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24085    language_registry.add(rust_lang());
24086    let mut fake_servers = language_registry.register_fake_lsp(
24087        "Rust",
24088        FakeLspAdapter {
24089            capabilities: lsp::ServerCapabilities {
24090                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24091                ..lsp::ServerCapabilities::default()
24092            },
24093            name: "rust-analyzer",
24094            ..FakeLspAdapter::default()
24095        },
24096    );
24097    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24098        "Rust",
24099        FakeLspAdapter {
24100            capabilities: lsp::ServerCapabilities {
24101                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24102                ..lsp::ServerCapabilities::default()
24103            },
24104            name: "not-rust-analyzer",
24105            ..FakeLspAdapter::default()
24106        },
24107    );
24108
24109    let editor = workspace
24110        .update(cx, |workspace, window, cx| {
24111            workspace.open_abs_path(
24112                PathBuf::from(path!("/a/first.rs")),
24113                OpenOptions::default(),
24114                window,
24115                cx,
24116            )
24117        })
24118        .unwrap()
24119        .await
24120        .unwrap()
24121        .downcast::<Editor>()
24122        .unwrap();
24123    let fake_language_server = fake_servers.next().await.unwrap();
24124    let fake_language_server_without_capabilities =
24125        fake_servers_without_capabilities.next().await.unwrap();
24126    let requests_made = Arc::new(AtomicUsize::new(0));
24127    let closure_requests_made = Arc::clone(&requests_made);
24128    let mut color_request_handle = fake_language_server
24129        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24130            let requests_made = Arc::clone(&closure_requests_made);
24131            async move {
24132                assert_eq!(
24133                    params.text_document.uri,
24134                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
24135                );
24136                requests_made.fetch_add(1, atomic::Ordering::Release);
24137                Ok(vec![
24138                    lsp::ColorInformation {
24139                        range: lsp::Range {
24140                            start: lsp::Position {
24141                                line: 0,
24142                                character: 0,
24143                            },
24144                            end: lsp::Position {
24145                                line: 0,
24146                                character: 1,
24147                            },
24148                        },
24149                        color: lsp::Color {
24150                            red: 0.33,
24151                            green: 0.33,
24152                            blue: 0.33,
24153                            alpha: 0.33,
24154                        },
24155                    },
24156                    lsp::ColorInformation {
24157                        range: lsp::Range {
24158                            start: lsp::Position {
24159                                line: 0,
24160                                character: 0,
24161                            },
24162                            end: lsp::Position {
24163                                line: 0,
24164                                character: 1,
24165                            },
24166                        },
24167                        color: lsp::Color {
24168                            red: 0.33,
24169                            green: 0.33,
24170                            blue: 0.33,
24171                            alpha: 0.33,
24172                        },
24173                    },
24174                ])
24175            }
24176        });
24177
24178    let _handle = fake_language_server_without_capabilities
24179        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24180            panic!("Should not be called");
24181        });
24182    cx.executor().advance_clock(Duration::from_millis(100));
24183    color_request_handle.next().await.unwrap();
24184    cx.run_until_parked();
24185    assert_eq!(
24186        1,
24187        requests_made.load(atomic::Ordering::Acquire),
24188        "Should query for colors once per editor open"
24189    );
24190    editor.update_in(cx, |editor, _, cx| {
24191        assert_eq!(
24192            vec![expected_color],
24193            extract_color_inlays(editor, cx),
24194            "Should have an initial inlay"
24195        );
24196    });
24197
24198    // opening another file in a split should not influence the LSP query counter
24199    workspace
24200        .update(cx, |workspace, window, cx| {
24201            assert_eq!(
24202                workspace.panes().len(),
24203                1,
24204                "Should have one pane with one editor"
24205            );
24206            workspace.move_item_to_pane_in_direction(
24207                &MoveItemToPaneInDirection {
24208                    direction: SplitDirection::Right,
24209                    focus: false,
24210                    clone: true,
24211                },
24212                window,
24213                cx,
24214            );
24215        })
24216        .unwrap();
24217    cx.run_until_parked();
24218    workspace
24219        .update(cx, |workspace, _, cx| {
24220            let panes = workspace.panes();
24221            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24222            for pane in panes {
24223                let editor = pane
24224                    .read(cx)
24225                    .active_item()
24226                    .and_then(|item| item.downcast::<Editor>())
24227                    .expect("Should have opened an editor in each split");
24228                let editor_file = editor
24229                    .read(cx)
24230                    .buffer()
24231                    .read(cx)
24232                    .as_singleton()
24233                    .expect("test deals with singleton buffers")
24234                    .read(cx)
24235                    .file()
24236                    .expect("test buffese should have a file")
24237                    .path();
24238                assert_eq!(
24239                    editor_file.as_ref(),
24240                    Path::new("first.rs"),
24241                    "Both editors should be opened for the same file"
24242                )
24243            }
24244        })
24245        .unwrap();
24246
24247    cx.executor().advance_clock(Duration::from_millis(500));
24248    let save = editor.update_in(cx, |editor, window, cx| {
24249        editor.move_to_end(&MoveToEnd, window, cx);
24250        editor.handle_input("dirty", window, cx);
24251        editor.save(
24252            SaveOptions {
24253                format: true,
24254                autosave: true,
24255            },
24256            project.clone(),
24257            window,
24258            cx,
24259        )
24260    });
24261    save.await.unwrap();
24262
24263    color_request_handle.next().await.unwrap();
24264    cx.run_until_parked();
24265    assert_eq!(
24266        3,
24267        requests_made.load(atomic::Ordering::Acquire),
24268        "Should query for colors once per save and once per formatting after save"
24269    );
24270
24271    drop(editor);
24272    let close = workspace
24273        .update(cx, |workspace, window, cx| {
24274            workspace.active_pane().update(cx, |pane, cx| {
24275                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24276            })
24277        })
24278        .unwrap();
24279    close.await.unwrap();
24280    let close = workspace
24281        .update(cx, |workspace, window, cx| {
24282            workspace.active_pane().update(cx, |pane, cx| {
24283                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24284            })
24285        })
24286        .unwrap();
24287    close.await.unwrap();
24288    assert_eq!(
24289        3,
24290        requests_made.load(atomic::Ordering::Acquire),
24291        "After saving and closing all editors, no extra requests should be made"
24292    );
24293    workspace
24294        .update(cx, |workspace, _, cx| {
24295            assert!(
24296                workspace.active_item(cx).is_none(),
24297                "Should close all editors"
24298            )
24299        })
24300        .unwrap();
24301
24302    workspace
24303        .update(cx, |workspace, window, cx| {
24304            workspace.active_pane().update(cx, |pane, cx| {
24305                pane.navigate_backward(&Default::default(), window, cx);
24306            })
24307        })
24308        .unwrap();
24309    cx.executor().advance_clock(Duration::from_millis(100));
24310    cx.run_until_parked();
24311    let editor = workspace
24312        .update(cx, |workspace, _, cx| {
24313            workspace
24314                .active_item(cx)
24315                .expect("Should have reopened the editor again after navigating back")
24316                .downcast::<Editor>()
24317                .expect("Should be an editor")
24318        })
24319        .unwrap();
24320    color_request_handle.next().await.unwrap();
24321    assert_eq!(
24322        3,
24323        requests_made.load(atomic::Ordering::Acquire),
24324        "Cache should be reused on buffer close and reopen"
24325    );
24326    editor.update(cx, |editor, cx| {
24327        assert_eq!(
24328            vec![expected_color],
24329            extract_color_inlays(editor, cx),
24330            "Should have an initial inlay"
24331        );
24332    });
24333}
24334
24335#[gpui::test]
24336async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24337    init_test(cx, |_| {});
24338    let (editor, cx) = cx.add_window_view(Editor::single_line);
24339    editor.update_in(cx, |editor, window, cx| {
24340        editor.set_text("oops\n\nwow\n", window, cx)
24341    });
24342    cx.run_until_parked();
24343    editor.update(cx, |editor, cx| {
24344        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24345    });
24346    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24347    cx.run_until_parked();
24348    editor.update(cx, |editor, cx| {
24349        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24350    });
24351}
24352
24353#[gpui::test]
24354async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
24355    init_test(cx, |_| {});
24356
24357    cx.update(|cx| {
24358        register_project_item::<Editor>(cx);
24359    });
24360
24361    let fs = FakeFs::new(cx.executor());
24362    fs.insert_tree("/root1", json!({})).await;
24363    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
24364        .await;
24365
24366    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
24367    let (workspace, cx) =
24368        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24369
24370    let worktree_id = project.update(cx, |project, cx| {
24371        project.worktrees(cx).next().unwrap().read(cx).id()
24372    });
24373
24374    let handle = workspace
24375        .update_in(cx, |workspace, window, cx| {
24376            let project_path = (worktree_id, "one.pdf");
24377            workspace.open_path(project_path, None, true, window, cx)
24378        })
24379        .await
24380        .unwrap();
24381
24382    assert_eq!(
24383        handle.to_any().entity_type(),
24384        TypeId::of::<InvalidBufferView>()
24385    );
24386}
24387
24388#[track_caller]
24389fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24390    editor
24391        .all_inlays(cx)
24392        .into_iter()
24393        .filter_map(|inlay| inlay.get_color())
24394        .map(Rgba::from)
24395        .collect()
24396}